<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Blog on Clément Joly – Open-Source, Rust &amp; SQLite</title><link>https://cj.rs/blog/</link><description>Recent content in Blog on Clément Joly – Open-Source, Rust &amp; SQLite</description><image><title>Clément Joly – Open-Source, Rust &amp; SQLite</title><url>https://cj.rs/images/open-graph-pages.jpg</url><link>https://cj.rs/images/open-graph-pages.jpg</link></image><generator>Hugo</generator><language>en</language><copyright>Clément Joly</copyright><lastBuildDate>Thu, 28 May 2026 23:48:29 +0000</lastBuildDate><atom:link href="https://cj.rs/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Advanced filtering in Git commands</title><link>https://cj.rs/blog/pathspec/</link><pubDate>Mon, 04 May 2026 19:09:13 +0100</pubDate><guid>https://cj.rs/blog/pathspec/</guid><description>Learning more about Git pathspec: negative patterns, attributes...</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>You can use negative filters like</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git diff <span style="color:#98c379">&#39;:!*.lock&#39;</span>
</span></span><span style="display:flex;"><span>git status <span style="color:#98c379">&#39;:!*.lock&#39;</span>
</span></span></code></pre></div><p>and on attributes:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git status <span style="color:#98c379">&#39;:(attr:!linguist-generated !linguist-vendored)&#39;</span>
</span></span></code></pre></div><p>You can even combine those patterns.</p>
  </div>



<h2 id="excluding-on-a-glob">Excluding on a glob</h2>
<p>I sometimes have many changes over multiple files in a Git repository. And then I want to display only the changes made to files in the <code>src/</code> directory:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git diff <span style="color:#98c379">&#39;*/src/*&#39;</span>
</span></span></code></pre></div><p>Or, more interesting, I want all changes except those made to lock files:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>git diff <span style="color:#98c379">&#39;:!*.lock&#39;</span>
</span></span></code></pre></div><p>Note that in the above examples, patterns are quoted, so that the shell does not expand the glob itself.
Here, Git is interpreting the patterns<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
This feature is called <a href="https://git-scm.com/docs/gitglossary#def_pathspec">“pathspec”</a>.</p>
<h2 id="diving-into-pathspec">Diving into pathspec</h2>
<p>That feature can be used with commands like <code>git ls-files</code>, <code>git status</code>, <code>git grep</code>&hellip;
And you can do more than exclude globs.</p>
<p>The above <code>:!*.lock</code> is actually a short form of <code>:(exclude)*.lock</code>. And quite a few other keywords exist, like <code>top</code> to force a match on the full path from the root of the repository, <code>icase</code> for a case insensitive match or <code>attr</code> to place particular constraints on the <a href="https://git-scm.com/docs/gitattributes">attributes</a> of a file.</p>
<p>For example, you can use the <a href="https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github">linguist</a> attributes understood by GitHub and other forges locally with:</p>
<ul>
<li><code>':(attr:!linguist-generated !linguist-vendored)'</code> matches files that have neither of these linguist attributes,</li>
<li><code>':(attr:linguist-generated)' ':(attr:linguist-vendored)'</code> matches files with either attribute<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>,</li>
<li>and that can be combined with file patterns and other attributes, like <code>':(icase,attr:linguist-vendored)*/LOCK'</code> which would match a file named <code>lock</code> or <code>Lock</code> or even <code>LoCk</code> if it has the <code>linguist-vendored</code> attribute.</li>
</ul>
<p>The <a href="https://git-scm.com/docs/gitglossary#def_pathspec">manual</a> contains even more details, like attributes gotchas, matching without interpreting globs&hellip;. Have fun!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Although, we could remove the quotes and just use the shell in the first example. It’s likely to be slower because the shell would consider all files, while Git can use gitignore, look for changed files first&hellip;&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Usually files don’t have both attributes, but if you wanted to match files with both attributes set, you could use <code>':(attr:linguist-generated linguist-vendored)'</code>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Features I want to try first in Neovim 0.12</title><link>https://cj.rs/blog/nvim0.12/</link><pubDate>Sun, 29 Mar 2026 20:06:06 +0100</pubDate><guid>https://cj.rs/blog/nvim0.12/</guid><description>Auto-completion without plugins, improved UI core, plugin management and more...</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-comment">
    <p class="alert-heading">
      💬
      
        This is a comment on
      
    </p>
    <p><a href="https://neovim.io/doc/user/news-0.12/">Neovim 0.12 News</a> (<a href="https://web.archive.org/web/20260329192230/https://neovim.io/doc/user/news/">archive</a>)</p>
  </div>



<p><a href="https://github.com/neovim/neovim/releases/tag/v0.12.0">Neovim 0.12</a> was released earlier today.
I don’t have enough time just now, but I’m sharing here the list of things from <a href="https://neovim.io/doc/user/news-0.12/">the release page</a> that I want to try first.</p>
<h2 id="whole-new-features">Whole new features</h2>
<p>Here are new features to replace some plugins and simplify my configuration:</p>
<ul>
<li>There is a new <code>'autocomplete'</code> option. When set, completion suggestions appear in insert mode, without pressing a triggering shortcut. The <code>'complete'</code> option also gains the ability to call arbitrary functions, as long as they follow the <a href="https://neovim.io/doc/user/insert/#complete-functions"><code>complete-functions</code></a> interface. These two features combined could replace the many completion plugins that came and went over the years. I certainly hope to use them to remove mini.completion from my config.<br>
More importantly, they might help the ecosystem to standardize around a common interface for completion sources. At the moment, LSP servers play this role, but a full-blown LSP server implementation in every plugin is quite heavy. For example, <a href="https://github.com/saecki/crates.nvim">crates.nvim</a> supports some completion plugins (nvim-cmp, coq.nvim) and also exposes an LSP server for cross-compatibility support. In the future, such a crate might only implement the <code>complete-functions</code> interface and it could be used as a source by either native nvim completions or plugins.</li>
<li>The other big highlight of this release is the native plugin manager, <a href="https://neovim.io/doc/user/pack/#vim.pack"><code>vim.pack</code></a>. It was contributed mainly by Evgeni Chasnovski (known for mini.nvim). <a href="https://echasnovski.com/blog/2026-03-13-a-guide-to-vim-pack">His guide of this new component is worth a read</a>. In particular, you can pin plugins to a particular hash, for a version you have audited and update only once you have audited the changes in any new version.</li>
<li>The default status line was reworked and integrates with <code>vim.diagnostic.status()</code>, <code>vim.ui.progress_status()</code> and an indicator for the new <code>busy</code> state. This will allow me to remove some custom logic to produce diagnostic status (<code>E:2 W:3</code> for 2 errors and 3 warnings) and maybe even drop my custom status line code entirely.</li>
<li>I will try to use the <code>MarkSet</code> to make user-placed marks visible in the gutter of the current buffer. I used plugins for that feature in the past, but it should be simple to implement it in configuration with <code>MarkSet</code> now.</li>
<li>I’ll use <code>vim.net.request()</code> to replace some calls to external commands. It will make the configuration more portable and shorter — it might even be marginally faster, eliminating an external command call.</li>
<li><code>'diffopt'</code> <code>inline</code> and <code>inline:word</code> will provide richer diff at the line level, a bit like <a href="https://dandavison.github.io/delta/">delta</a>.</li>
<li>It’s nice to have native plugins for the undo tree (<code>:Undotree</code>) and to compare whole folders (<code>:DiffTool</code>).</li>
<li>I plan to use the new treesitter selection shortcuts (<code>an</code>, <code>in</code>, <code>[n</code> and <code>]n</code> in visual mode) to replace the deprecated <a href="https://github.com/mfussenegger/nvim-treehopper">treehopper</a> plugin: I don’t really care about jumping to a particular highlight node, visual mode should work better for me.</li>
</ul>
<h2 id="polish-of-existing-features">Polish of existing features</h2>
<p>This release introduces some nice <a href="https://neovim.io/doc/user/news-0.12/#_performance">performance improvements</a>, for instance on <code>Ctrl+r</code> in insert mode and <code>packadd</code>.</p>
<p>Some handy shortcuts were added:</p>




  <figure>
    <blockquote cite="https://neovim.io/doc/user/news-0.12/">
      <ul>
<li><code>grt</code> in Normal mode maps to vim.lsp.buf.type_definition()</li>
<li><code>grx</code> in Normal mode maps to vim.lsp.codelens.run()</li>
<li><code>gx</code> in help buffers opens the online documentation for the tag at cursor</li>
</ul>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://neovim.io/doc/user/news-0.12/">https://neovim.io/doc/user/news-0.12/</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Finally, the UI was reworked: it is now less coupled with the core. So the core can be <code>:restart</code>ed or <code>:connect</code>ed to with the same UI.
The <code>ui2</code> opt-in should also remove the dreaded “Press ENTER” message.
I plan to give it a try and go back to <code>cmdheight=0</code>.
Both of these settings are marked experimental, but if they are stable enough for me, I might get one more line for files.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2026-03-30: Changed <a href="https://neovim.io/doc/user/news/">https://neovim.io/doc/user/news/</a> to point to <a href="https://neovim.io/doc/user/news-0.12/">https://neovim.io/doc/user/news-0.12/</a>, since the former changed after publishing this post. Further explain why I think <code>complete</code>/<code>autocomplete</code> could help standardize the ecosystem.
2026-04-05: Add a link to <a href="https://neovim.io/doc/user/pack/#vim.pack">vim.pack</a></p>
  </div>



]]></content:encoded></item><item><title>DNS-PERSIST-01: Safer Wildcard Certificates</title><link>https://cj.rs/blog/wildcard/</link><pubDate>Sun, 22 Feb 2026 06:45:30 +0000</pubDate><guid>https://cj.rs/blog/wildcard/</guid><description>No need to put sensitive DNS write API tokens all over the place</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-comment">
    <p class="alert-heading">
      💬
      
        This is a comment on
      
    </p>
    <p><a href="https://letsencrypt.org/2026/02/18/dns-persist-01.html">DNS-PERSIST-01: A New Model for DNS-based Challenge Validation</a></p>
  </div>



<h2 id="the-source-of-truth">The Source of Truth</h2>
<p>Connecting to a website?
Sending an email?
Which server<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> you reach depends on DNS records.
And <a href="https://en.wikipedia.org/wiki/DNS_Certification_Authority_Authorization">CAA</a> or <a href="https://en.wikipedia.org/wiki/SSHFP_record">SSHFP</a> records establish trust for public key cryptographic protocols.</p>
<p>DNS records are the source of truth.</p>
<h2 id="dns-api-tokens">DNS API Tokens</h2>
<p>Currently, wildcard certificates with Let’s Encrypt <a href="https://letsencrypt.org/docs/faq/#does-let-s-encrypt-issue-wildcard-certificates">require</a> to <a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge">write an arbitrary string in a DNS record</a>, for every single certificate renewed or issued.
In practice, this often involves sharing a <em>write</em> API token to change the DNS.
This is risky: should an attacker obtain the token, they can do a lot of damage: hijack traffic (and get valid certificates for it), receive and send emails using the corresponding domain&hellip;</p>
<p>And often API tokens are not scoped just to access one domain, through human error or because the provider lacks the feature.
Then, they might give access to all domains in an account at the DNS provider and the attacker can move laterally to more targets.</p>
<p>With this in mind, I have not set up wildcard certificates with Let’s Encrypt so far.
The risk simply outweighed the gains for the use cases I had.
And I’m not alone: <a href="https://github.com/krtab/agnos">agnos</a> was written to address the same concern.
And the <a href="https://letsencrypt.org/docs/challenge-types/#dns-01-challenge">DNS challenge documentation calls this out</a>: “Keeping API credentials on your web server is risky”.</p>
<h2 id="dns-challenges-without-tokens">DNS Challenges Without Tokens</h2>
<p>So I’m really excited about this new challenge type, <a href="https://letsencrypt.org/2026/02/18/dns-persist-01.html"><code>DNS-PERSIST-01</code></a>.
It is a <em>static</em> DNS TXT record that authorizes a particular Let’s Encrypt account<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> to get certificates, even wildcard ones, for a particular domain.
No need to share very sensitive DNS API token!<br>
It also means that Let’s Encrypt clients don’t need to support the API of your DNS provider to emit wildcard certificates.
Pick any client and DNS provider you want, including a self hosted one.</p>
<p>Looking forward to this being fully implemented and available to everyone!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Or at least it’s IP&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Granted, an account is tied to a private key on the machine requesting the certificate. But this is a lot less sensitive, it’s only used by Let’s Encrypt, not for everything attached to a domain.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>6-day Certificates With Let’s Encrypt and Caddy</title><link>https://cj.rs/blog/6days/</link><pubDate>Sat, 17 Jan 2026 13:36:24 +0000</pubDate><guid>https://cj.rs/blog/6days/</guid><description>First impressions</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-comment">
    <p class="alert-heading">
      💬
      
        This is a comment on
      
    </p>
    <p><a href="https://letsencrypt.org/2026/01/15/6day-and-ip-general-availability.html">6-day and IP Address Certificates are Generally Available</a> by Matthew McPherrin (<a href="https://lobste.rs/s/boyarm/6_day_ip_address_certificates_are">via</a>)</p>
  </div>



<p>Let’s Encrypt has just announced that short-lived<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> certificates are generally available.
They can also be used for IP addresses, which is especially useful for <a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS">DNS over HTTPS</a>.
Those certificates <em>could</em> be smaller in the future, if information for validity checks is omitted.
However, for now at least, these certificates still <a href="https://letsencrypt.org/docs/profiles/#shortlived">include revocation information</a>.</p>
<h2 id="using-those-new-certificates-with-caddy">Using Those New Certificates With Caddy</h2>
<p>I wanted to give those certificates a try with Caddy.
Caddy needs to use the <a href="https://letsencrypt.org/docs/profiles/#shortlived"><code>shortlived</code></a> Let’s Encrypt profile.
The profile feature isn’t explicitly mentioned in the <a href="https://caddyserver.com/docs/caddyfile/directives/tls#issuers">docs</a>, but some <a href="https://caddy.community/t/using-setting-acme-profiles-as-of-2-10/31006">community posts</a> use a <code>profile</code> directive.
This also works in the global configuration:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Caddy" data-lang="Caddy"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>	<span style="color:#c678dd">cert_issuer</span> <span style="color:#98c379">acme</span> {
</span></span><span style="display:flex;"><span>		<span style="color:#c678dd">profile</span> <span style="color:#98c379">shortlived</span>
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Reloading the Caddy configuration was not enough to prompt an early renewal, so I deleted the underlying certificate files and restarted Caddy:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>sudo rm -rf /var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/example.com
</span></span><span style="display:flex;"><span>sudo systemctl restart caddy
</span></span></code></pre></div><p>And I got a brand new certificate for my domain name, valid only for a few days. It’s marginally smaller<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> than with the <code>classic</code> profile, mostly because these short-lived certificates also use the <a href="https://letsencrypt.org/2025/11/24/gen-y-hierarchy">new generation hierarchy</a>. But they should be roughly the same size as the <code>tlsserver</code> profile.</p>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p><a href="https://github.com/caddyserver/caddy/issues/7399">Certificates against IPv6 addresses</a> (i.e. not against a domain using IPv6) seem to require an unreleased version of Caddy.</p>
  </div>



<h2 id="conclusion">Conclusion</h2>
<p>With Caddy, certificate renewal should be automated enough that short-lived certificates don’t cause any problems.
But I’ll see with this experiment if there are any surprising pain points.</p>
<p>My understanding is that browsers only cache TLS sessions, not certificates: when a full handshake is performed, the full certificate chain is then sent anyway<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.
So even if the certificate expires more frequently, it is not sent more often.
The slightly smaller certificate chain is thus a small net benefit.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The certificates are valid for about 6 days and precisely 160 hours. 160 is the sum of the eleven first prime numbers (2+3+5+7+11+13+17+19+23+29+31) and of the cube of the first three. How cool is that!&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Comparing the .pem files as a proxy, 1.2k vs 1.3k for the bare certificate, 4.0k vs 4.8k for the whole chain&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>According to <a href="https://stackoverflow.com/a/52610552">this StackOverflow answer</a> and <a href="https://www.cloudflare.com/learning/ssl/what-happens-in-a-tls-handshake/">this Cloudflare explanation</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Re: Short URLs</title><link>https://cj.rs/blog/su/</link><pubDate>Sun, 04 Jan 2026 16:27:23 +0000</pubDate><guid>https://cj.rs/blog/su/</guid><description>Adopting shorter &amp;amp; more meaningful URLs on my website for 2026!</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-comment">
    <p class="alert-heading">
      💬
      
        This is a comment on
      
    </p>
    <p><a href="https://sive.rs/su">Short URLs: why and how</a> by Derek Sivers</p>
  </div>



<h2 id="in-the-beginning-were-short-urls">In the Beginning Were Short URLs</h2>
<p>I recently read Derek Sivers’s <a href="https://sive.rs/su">Short URLs: why and how</a>, <em>again</em>.
He makes a compelling case for using very short, but meaningful, URLs on your website.
Very short here means one or two words at most, or even just 2 to 4 characters.
On his website, that’s <a href="https://sive.rs/anna">https://sive.rs/anna</a>, <a href="https://sive.rs/plaintext">https://sive.rs/plaintext</a> <a href="https://sive.rs/1s">https://sive.rs/1s</a>, or indeed <a href="https://sive.rs/su">https://sive.rs/su</a> for that particular post. That’s very different from typical URLs like <a href="https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/">https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> or <a href="https://github.blog/changelog/2021-10-11-improved-notification-email-titles-for-issues-and-prs/">https://github.blog/changelog/2021-10-11-improved-notification-email-titles-for-issues-and-prs/</a>.</p>
<p>Similarly, before I even read that post, the first few pages I created on my website had similar URLs: <a href="https://cj.rs/cv/">/cv</a> or <a href="https://cj.rs/open-source/">/open-source</a>.
Why?
It’s easy to remember and you can type it (quickly!).
They are also easy to share on the phone or face to face.
Someone asks for my CV?
Easy, go to <code>cj.rs/cv</code> (<code>Charlie Juliett DOT Romeo Sierra SLASH Charlie Victor</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> on the phone).</p>
<p>Short URLs also look nice and tight.
I even purchased <code>cj.rs</code> to replace <code>joly.pw</code> as the main domain in 2019<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<p>Derek makes similar points:</p>




  <figure>
    <blockquote cite="https://sive.rs/su">
      <ul>
<li><strong>I can remember my URLs.</strong> I can avoid the search engine step. (No
need to search when I already know the answer.) Which means&hellip;</li>
<li><strong>I can type it or say it.</strong> Whether texting, answering an email, or
talking to someone on the phone, I can say, &ldquo;Go to <a href="https://sieve.rs/ff">sive.rs/ff</a>
for my talk about the first follower.&rdquo; or &ldquo;My newest book is at
<a href="https://sieve.rs/h">sive.rs/h</a>.&rdquo; I do this often, so having memorable URLs, <strong>easy
to type in full</strong>, saves me a lot of searching.</li>
<li><strong>They look nicer.</strong> They&rsquo;re aesthetic. They show care. We should
put something of beauty into the world, instead of creating <a href="/polut">digital
pollution</a>.</li>
</ul>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://sive.rs/su">https://sive.rs/su</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>He then adds:</p>




  <figure>
    <blockquote cite="https://sive.rs/su">
      <ul>
<li><strong>They remove the middle-man.</strong> With <em>long</em> URLs, people use those
ugly social share buttons that promote (and further entrench)
harmful social media sites, and add visual clutter to your site.
Short URLs encourage people to copy and paste the URL directly,
which lets them share it anywhere, instead of only the sites for
which you have a share button.</li>
<li><strong>They&rsquo;re enough.</strong> Using 36 characters (a-z and 0-9):<br>
2-character URLs give me 1296 (36²) unique combinations.<br>
3-character URLs give me 46,656 (36³) unique combinations.<br>
4-character URLs give me 1,679,616 (36⁴) unique combinations.<br>
I don&rsquo;t need more than that.</li>
</ul>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://sive.rs/su">https://sive.rs/su</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>I did not really think about those last points<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, but they make sense as nice complementary benefits.</p>
<h2 id="losing-my-way-and-finding-it-back">Losing My Way and Finding It Back</h2>
<p>If we were thinking alike about short URLs, then why did I use <a href="https://cj.rs/blog/lets-encrypt-caa-records-with-caddy/">https://cj.rs/blog/lets-encrypt-caa-records-with-caddy/</a> for my last blog post?
Why did I add the full title at the end of a path that is nested inside blog?
Shouldn’t I have used something like <code>letsencrypt-caa</code> or <code>caddy-caa</code>?</p>
<p>Mainly I lost sight of the benefits of concise URLs.
And <a href="https://gohugo.io/">Hugo</a> has nice <a href="https://gohugo.io/quick-reference/glossary/#section-page">sections</a> to organise content, set cascading parameters, list related pages…</p>
<p>But regardless of the tool, I certainly think in terms of categories for my pages.
I remember things like “I have written this blog post” or “I have created this page at the root to showcase my open source work”.</p>
<p>A bit of hierarchical organisation makes sense even for the user.
Some pages share a common theme, but in different categories.
For instance, I have <a href="https://cj.rs/blog/new-asciinema-hugo-module-versions/">a blog post on an Asciinema module for Hugo</a> and <a href="https://cj.rs/gohugo-asciinema/">the page of that project</a> both pertaining to the same underlying project.
So ideally, they would be at <code>cj.rs/hugo-asciinema</code> and <code>cj.rs/blog/hugo-asciinema</code> instead of <code>cj.rs/hugo-asciinema</code> and <code>cj.rs/announcement-hugo-asciinema</code>.</p>
<p>Finally, even when there is only one page on a particular topic, some hierarchy is useful.
Something like <code>cj.rs/su</code> can look cryptic in a tweet, whereas <code>cj.rs/blog/su</code> makes more sense.
Both can still be easily remembered and typed.
And having <code>/blog/</code> for blog posts leaves space right under <code>/</code> for proper pages on the same topic that may exist later.
While there are over one million 4-letter combinations in a-z and 0-9, meaningful words are much rarer.
On the same example as above, I’m keeping <code>/asciinema</code> in store for something more closely related to Asciinema in the future.
If I had spent that one word on the Hugo module, it would have been gone forever<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<h2 id="striking-a-balance">Striking a Balance</h2>
<p>Moving forward, I’ll revert to much shorter URLs using (for the path part):</p>
<ul>
<li>One or two words at the top level, for projects and generally pages that are not blog posts. Think <code>/contact</code>, <code>/rusqlite-migration</code> or <code>/open-source</code>.</li>
<li>At most one folder, like <code>/blog/</code> for blog posts. The exception would be multi-parts articles, which could be under <code>/blog/topic/1</code>, <code>/blog/topic/2</code>…</li>
<li>That list may evolve if there are strong reasons, but I’ll keep in mind the memorability and direct typing benefits.</li>
</ul>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p>As a side note, I think short URLs work even if the domain is made of multiple words like <code>shinychair.com</code>, <code>blog.joinmastodon.org</code>, <code>alostgardener.blog</code>. Even with these domains, short URLs convey meaning, are still relatively easy to type and are easy to remember for the site owner.</p>
  </div>



<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>An interesting read of it’s own, I should perhaps blog about it some day. But in the meantime, feel free to go and read it, I’ll wait.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I keep a letter-word table handy at &lt;<a href="https://cj.rs/nato">/nato</a>&gt;.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>At least according to a whois look up.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Although <code>cj.rs</code> looks like a URL shortener and URL shorteners were the inspiration to look for niche TLDs. For the first couple months, I have to admit I couldn’t get over how cool a two letters domain felt. I now think it is a bit unfortunate to use the Republic of Serbia’s TLD, instead of, say, a <code>.org</code>, which seems less likely to implement residency requirements.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Don’t change URLs: <a href="https://www.w3.org/Provider/Style/URI">Hypertext Style: Cool URIs don&rsquo;t change.</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Let's Encrypt CAA Records with Caddy</title><link>https://cj.rs/blog/lets-encrypt-caa-records-with-caddy/</link><pubDate>Tue, 25 Feb 2025 19:42:01 +0000</pubDate><guid>https://cj.rs/blog/lets-encrypt-caa-records-with-caddy/</guid><description>Limiting certificate issuance to a server that possesses a particular private key</description><content:encoded><![CDATA[<h2 id="a-man-in-the-middle-attack">A Man-in-the-Middle Attack</h2>
<p>I host my own instance of <a href="https://miniflux.app/">miniflux</a>, an RSS reader.
I do it as a hobby, I enjoy the learning opportunities that come along the way.</p>
<p>One such opportunity presented itself in November 2023.</p>
<p>Back then, a Man-in-the-Middle attack was <a href="https://notes.valdikss.org.ru/jabber.ru-mitm">reported against jabber.ru</a>.
You can go read the full details on that blog post, but let’s go over its main aspects.
Without the attacker, a client connects directly to the <code>jabber.ru</code> server over TLS:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 648 73"
      >
      <g transform='translate(8,16)'>
<path d='M 16,16 L 96,16' fill='none' stroke='currentColor'></path>
<path d='M 480,16 L 632,16' fill='none' stroke='currentColor'></path>
<path d='M 112,32 L 472,32' fill='none' stroke='currentColor'></path>
<path d='M 16,48 L 96,48' fill='none' stroke='currentColor'></path>
<path d='M 480,48 L 632,48' fill='none' stroke='currentColor'></path>
<path d='M 480,16 L 480,32' fill='none' stroke='currentColor'></path>
<path d='M 480,32 L 480,48' fill='none' stroke='currentColor'></path>
<path d='M 632,16 L 632,48' fill='none' stroke='currentColor'></path>
<polygon points='480.000000,32.000000 468.000000,26.400000 468.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 472.000000, 32.000000)'></polygon>
<path d='M 16,16 A 16,16 0 0,0 0,32' fill='none' stroke='currentColor'></path>
<path d='M 96,16 A 16,16 0 0,1 112,32' fill='none' stroke='currentColor'></path>
<path d='M 0,32 A 16,16 0 0,0 16,48' fill='none' stroke='currentColor'></path>
<path d='M 112,32 A 16,16 0 0,1 96,48' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='248' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='256' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='264' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='272' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='272' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='280' y='4' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='280' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='288' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='288' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='296' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='304' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='312' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='320' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='36' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='504' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='512' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='520' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='528' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='536' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='544' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='552' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='560' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='576' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='584' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='592' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='600' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='608' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='616' y='36' fill='currentColor' style='font-size:1em'>r</text>
</g>

    </svg>
  
</div>
<p>TLS encrypts the connection.
A certificate is also presented when the connection is established, to ensure authenticity and integrity.
For this to work, the Certificate Authority (CA), a trusted third party, signs the certificate.
In doing so, it affirms that it saw a given certificate associated with a particular domain<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
This protects against some <a href="https://en.wikipedia.org/wiki/Man-in-the-middle_attack">Man-in-the-Middle attacks</a>, where an attacker inserts itself between a client (say on a public wifi network) and the website, as in the next diagram.
If that attacker does not manage to similarly be between the CA and the website, then it will be unable to decrypt or alter the exchange without being detected, because it can’t present a certificate signed by the CA.</p>
<p>Nonetheless in November 2023, a Man-in-the-Middle attack against <code>jabber.ru</code> succeeded.
In that instance, the attacker leveraged a privileged position on the network.
They intercepted all TLS traffic to the victim’s server, then tricked<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> the <a href="https://letsencrypt.org/">Let’s Encrypt</a> CA into issuing a certificate for the attacker server.
Then, they could decrypt the traffic from the client, re-encrypt it, potentially alter it and send it to the server.</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 648 73"
      >
      <g transform='translate(8,16)'>
<path d='M 16,16 L 96,16' fill='none' stroke='currentColor'></path>
<path d='M 224,16 L 368,16' fill='none' stroke='currentColor'></path>
<path d='M 480,16 L 632,16' fill='none' stroke='currentColor'></path>
<path d='M 112,32 L 216,32' fill='none' stroke='currentColor'></path>
<path d='M 368,32 L 472,32' fill='none' stroke='currentColor'></path>
<path d='M 16,48 L 96,48' fill='none' stroke='currentColor'></path>
<path d='M 224,48 L 368,48' fill='none' stroke='currentColor'></path>
<path d='M 480,48 L 632,48' fill='none' stroke='currentColor'></path>
<path d='M 224,16 L 224,32' fill='none' stroke='currentColor'></path>
<path d='M 224,32 L 224,48' fill='none' stroke='currentColor'></path>
<path d='M 368,16 L 368,32' fill='none' stroke='currentColor'></path>
<path d='M 368,32 L 368,48' fill='none' stroke='currentColor'></path>
<path d='M 480,16 L 480,32' fill='none' stroke='currentColor'></path>
<path d='M 480,32 L 480,48' fill='none' stroke='currentColor'></path>
<path d='M 632,16 L 632,48' fill='none' stroke='currentColor'></path>
<polygon points='224.000000,32.000000 212.000000,26.400000 212.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 216.000000, 32.000000)'></polygon>
<polygon points='480.000000,32.000000 468.000000,26.400000 468.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 472.000000, 32.000000)'></polygon>
<path d='M 16,16 A 16,16 0 0,0 0,32' fill='none' stroke='currentColor'></path>
<path d='M 96,16 A 16,16 0 0,1 112,32' fill='none' stroke='currentColor'></path>
<path d='M 0,32 A 16,16 0 0,0 16,48' fill='none' stroke='currentColor'></path>
<path d='M 112,32 A 16,16 0 0,1 96,48' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='16' y='36' fill='currentColor' style='font-size:1em'>X</text>
<text text-anchor='middle' x='24' y='36' fill='currentColor' style='font-size:1em'>M</text>
<text text-anchor='middle' x='32' y='36' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='40' y='36' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='56' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='64' y='36' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='72' y='36' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='80' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='88' y='36' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='96' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='128' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='136' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='144' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='152' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='152' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='4' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='160' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='168' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='168' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='176' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='184' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='192' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='200' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='240' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='248' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='256' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='264' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='272' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='280' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='288' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='296' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='312' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='320' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='328' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='336' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='344' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='352' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='384' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='392' y='4' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='392' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='400' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='400' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='408' y='4' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='424' y='4' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='432' y='4' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='440' y='4' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='440' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='448' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='456' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='36' fill='currentColor' style='font-size:1em'>j</text>
<text text-anchor='middle' x='504' y='36' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='512' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='520' y='36' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='528' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='536' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='544' y='36' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='552' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='560' y='36' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='576' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='584' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='592' y='36' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='600' y='36' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='608' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='616' y='36' fill='currentColor' style='font-size:1em'>r</text>
</g>

    </svg>
  
</div>
<p>There was no need to compromise the server for this attack to work.
It simply leveraged a privileged network position.</p>
<p>In a <a href="https://www.devever.net/~hl/xmpp-incident#narrow">reply on his blog</a>, ACME developer Hugo Landau points out the following mitigation:</p>




  <figure>
    <blockquote cite="https://www.devever.net/~hl/xmpp-incident#narrow">
      <p><strong>Mitigation.</strong> The second area of consideration is mitigation, in which the unauthorized issuance of TLS certificates is prevented from happening in the first place. The entire point of a TLS certificate is, of course, to prevent a man-in-the-middle attack. The fundamental problem here is that the “Domain Validation” model by which CAs validate control of a domain name is ironically itself vulnerable to man-in-the-middle attacks, especially if an attacker can intercept not just some but <em>all</em> traffic to a victim site (as happened in this case).</p>
<p>Some years ago I authored <a href="https://www.rfc-editor.org/rfc/rfc8657">ACME-CAA (RFC 8657)</a>, now <a href="https://www.devever.net/~hl/acme-caa-live">implemented</a> by Let&rsquo;s Encrypt, which can mitigate this in some circumstances. The basic idea is that you can configure a DNS record which specifies that only a <em>specific account</em> of a <em>specific CA</em> is authorised to issue certificates for a domain. Thus simply using the same CA isn&rsquo;t enough; you must gain access to the same account at the CA. With Let&rsquo;s Encrypt, this means gaining access to the ACME private key used to request a certificate. Based on what we know about the attack, it would have been prevented by deploying this extension.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://www.devever.net/~hl/xmpp-incident#narrow">https://www.devever.net/~hl/xmpp-incident#narrow</a></cite>
          <br/>
        
        
    <p>
          This is just one mitigation strategy and it has caveats. I encourage you to go read the rest of the post, where Hugo also explain how to detect the attack.
    </p>
        
      </figcaption>
    
  </figure>



<p>This got me thinking: how would I implement this mitigation for my self-hosted services?
In this post, we will use the <code>CAA</code> DNS record to limit issuance to one particular account registered at one particular CA, as suggested above.</p>
<h2 id="the-setup">The Setup</h2>
<h3 id="tls-proxy">TLS Proxy</h3>
<p>My setup looks like this:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 616 153"
      >
      <g transform='translate(8,16)'>
<path d='M 224,0 L 600,0' fill='none' stroke='currentColor'></path>
<path d='M 248,32 L 368,32' fill='none' stroke='currentColor'></path>
<path d='M 464,32 L 552,32' fill='none' stroke='currentColor'></path>
<path d='M 16,48 L 96,48' fill='none' stroke='currentColor'></path>
<path d='M 368,48 L 456,48' fill='none' stroke='currentColor'></path>
<path d='M 112,64 L 240,64' fill='none' stroke='currentColor'></path>
<path d='M 464,64 L 552,64' fill='none' stroke='currentColor'></path>
<path d='M 16,80 L 96,80' fill='none' stroke='currentColor'></path>
<path d='M 368,80 L 432,80' fill='none' stroke='currentColor'></path>
<path d='M 248,96 L 368,96' fill='none' stroke='currentColor'></path>
<path d='M 224,128 L 600,128' fill='none' stroke='currentColor'></path>
<path d='M 224,0 L 224,48' fill='none' stroke='currentColor'></path>
<path d='M 224,80 L 224,128' fill='none' stroke='currentColor'></path>
<path d='M 248,32 L 248,64' fill='none' stroke='currentColor'></path>
<path d='M 248,64 L 248,96' fill='none' stroke='currentColor'></path>
<path d='M 368,32 L 368,48' fill='none' stroke='currentColor'></path>
<path d='M 368,48 L 368,80' fill='none' stroke='currentColor'></path>
<path d='M 368,80 L 368,96' fill='none' stroke='currentColor'></path>
<path d='M 464,32 L 464,48' fill='none' stroke='currentColor'></path>
<path d='M 464,48 L 464,64' fill='none' stroke='currentColor'></path>
<path d='M 552,32 L 552,64' fill='none' stroke='currentColor'></path>
<path d='M 600,0 L 600,128' fill='none' stroke='currentColor'></path>
<path d='M 224,48 L 224,56' fill='none' stroke='currentColor'></path>
<path d='M 224,72 L 224,80' fill='none' stroke='currentColor'></path>
<polygon points='248.000000,64.000000 236.000000,58.400002 236.000000,69.599998' fill='currentColor' transform='rotate(0.000000, 240.000000, 64.000000)'></polygon>
<polygon points='440.000000,80.000000 428.000000,74.400002 428.000000,85.599998' fill='currentColor' transform='rotate(0.000000, 432.000000, 80.000000)'></polygon>
<polygon points='464.000000,48.000000 452.000000,42.400002 452.000000,53.599998' fill='currentColor' transform='rotate(0.000000, 456.000000, 48.000000)'></polygon>
<path d='M 16,48 A 16,16 0 0,0 0,64' fill='none' stroke='currentColor'></path>
<path d='M 96,48 A 16,16 0 0,1 112,64' fill='none' stroke='currentColor'></path>
<path d='M 0,64 A 16,16 0 0,0 16,80' fill='none' stroke='currentColor'></path>
<path d='M 112,64 A 16,16 0 0,1 96,80' fill='none' stroke='currentColor'></path>
<text text-anchor='middle' x='16' y='68' fill='currentColor' style='font-size:1em'>W</text>
<text text-anchor='middle' x='24' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='32' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='48' y='68' fill='currentColor' style='font-size:1em'>b</text>
<text text-anchor='middle' x='56' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='64' y='68' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='72' y='68' fill='currentColor' style='font-size:1em'>w</text>
<text text-anchor='middle' x='80' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='96' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='136' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='144' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='152' y='36' fill='currentColor' style='font-size:1em'>T</text>
<text text-anchor='middle' x='152' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='160' y='36' fill='currentColor' style='font-size:1em'>L</text>
<text text-anchor='middle' x='160' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='168' y='36' fill='currentColor' style='font-size:1em'>S</text>
<text text-anchor='middle' x='168' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='176' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='184' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='192' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='200' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='208' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='264' y='68' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='272' y='68' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='280' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='288' y='68' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='288' y='116' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='296' y='68' fill='currentColor' style='font-size:1em'>y</text>
<text text-anchor='middle' x='296' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='304' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='312' y='68' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='312' y='116' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='320' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='320' y='116' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='328' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='328' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='336' y='68' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='344' y='68' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='344' y='116' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='352' y='68' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='352' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='360' y='116' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='368' y='116' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='376' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='384' y='116' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='400' y='36' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='400' y='116' fill='currentColor' style='font-size:1em'>(</text>
<text text-anchor='middle' x='408' y='20' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='408' y='36' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='408' y='116' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='416' y='20' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='416' y='36' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='416' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='424' y='20' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='424' y='36' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='424' y='116' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='432' y='20' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='432' y='36' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='432' y='116' fill='currentColor' style='font-size:1em'>k</text>
<text text-anchor='middle' x='440' y='36' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='440' y='116' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='448' y='116' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='456' y='84' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='456' y='116' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='464' y='84' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='464' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='472' y='84' fill='currentColor' style='font-size:1em'>h</text>
<text text-anchor='middle' x='480' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='480' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='480' y='116' fill='currentColor' style='font-size:1em'>O</text>
<text text-anchor='middle' x='488' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='488' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='488' y='116' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='496' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='496' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='504' y='52' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='504' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='504' y='116' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='512' y='52' fill='currentColor' style='font-size:1em'>f</text>
<text text-anchor='middle' x='512' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='512' y='116' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='520' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='520' y='84' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='520' y='116' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='528' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='528' y='84' fill='currentColor' style='font-size:1em'>v</text>
<text text-anchor='middle' x='528' y='116' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='536' y='52' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='536' y='84' fill='currentColor' style='font-size:1em'>i</text>
<text text-anchor='middle' x='536' y='116' fill='currentColor' style='font-size:1em'>)</text>
<text text-anchor='middle' x='544' y='84' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='552' y='84' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='560' y='84' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='568' y='84' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='576' y='84' fill='currentColor' style='font-size:1em'>.</text>
<text text-anchor='middle' x='584' y='84' fill='currentColor' style='font-size:1em'>.</text>
</g>

    </svg>
  
</div>
<p>Here <a href="https://caddyserver.com/">caddy</a> acts as a TLS proxy.
It manages the TLS certificate, requesting a new one from Let’s Encrypt as needed. It also terminates the TLS connection from the browser and then connects to the underlying service, like miniflux.
This example could of course be generalized to any service proxied by caddy, in particular services handling more personal data, like a self-hosted webmail or contact server.</p>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p>Here, we do not have a CDN in front of our site.<br>
The whole point is to restrict the certificates issued to a particular Let’s Encrypt account that we control.
A CDN terminates the TLS connection (usually), so it needs a TLS certificates for the domain.</p>
<p>In this context, the server is presumably in one’s house or in a datacenter nearby.
There is thus less of a need for an intermediary cache that would be closer to the user.</p>
  </div>



<h3 id="dns">DNS</h3>
<p>The domain hosting the RSS reader, <code>r.cj.rs</code>, is in fact a <code>CNAME</code> record pointing to the underlying machine hosting the various services, Olivine.
It looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>dog r.cj.rs
</span></span></code></pre></div><pre tabindex="0"><code>CNAME r.cj.rs.             5m00s   &#34;olivine.joly.eu.org.&#34;
    A olivine.joly.eu.org. 5m00s   132.145.68.59
</code></pre><h2 id="caa-records"><code>CAA</code> Records</h2>
<p>Going back to <a href="https://www.devever.net/~hl/xmpp-incident#narrow">the mitigation</a> we want to implement:</p>




  <figure>
    <blockquote cite="https://www.devever.net/~hl/xmpp-incident#narrow">
      <p>The basic idea is that you can configure a DNS record which specifies that only a <em>specific account</em> of a <em>specific CA</em> is authorised to issue certificates for a domain.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://www.devever.net/~hl/xmpp-incident#narrow">https://www.devever.net/~hl/xmpp-incident#narrow</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Let’s Encrypt provides <a href="https://letsencrypt.org/docs/caa/">some documentation</a> for the <code>CAA</code> record (which stands for <em>Certification Authority Authorization</em>).
The key points are:</p>
<ul>
<li><code>CAA</code> records closest to the domain take priority: a <code>CAA</code> on <code>r.cj.rs</code> takes precedence over one on <code>cj.rs</code>.</li>
<li><code>CAA</code> follows <code>CNAME</code> redirects.</li>
<li>The <code>issue</code> and <code>issuewild</code> properties control the certificate authority allowed. If only <code>issue</code> is present, then the same constraints apply to both normal and wildcard certificates.</li>
<li>The <code>accounturi</code> parameter can restrict issuance to a particular account, like <code>https://acme-v02.api.letsencrypt.org/acme/acct/1234567890</code></li>
</ul>
<p>So in our case, we can simply add an <code>issue</code> <code>CAA</code> record with the <code>accounturi</code> parameter on the DNS record directly pointing to the machine (<code>olivine.joly.eu.org</code>).
Then other domains can alias with <code>CNAME</code> to that DNS record and will inherit the CAA constraints.
And if a service moves to a different server, the CAA constraint will be that of the new server, if any.</p>
<h3 id="finding-the-accounturi-caddy-uses">Finding the <code>accounturi</code> Caddy Uses</h3>
<p>Caddy automatically creates a Let’s Encrypt account if none is configured.
It then issues certificates against that account.
But where is the account number?</p>
<p>By default on Ubuntu, the parameters for that account are stored in <code>/var/lib/caddy/.local/share/caddy/acme/acme-v02.api.letsencrypt.org-directory/users/default/default.json</code><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.
There, the location key contains the accounturi:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#7f848e">// ...
</span></span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;location&#34;</span>: <span style="color:#98c379">&#34;https://acme-v02.api.letsencrypt.org/acme/acct/1968958296&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="putting-it-all-together">Putting It All Together</h2>
<p>Summing up the above, we know that we need a <code>CAA</code> record with the <code>issue</code> property and the same <code>accounturi</code> as caddy.
It looks like this in the Bind format:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bind" data-lang="bind"><span style="display:flex;"><span><span style="color:#c678dd">IN</span> <span style="color:#c678dd">CAA</span>   <span style="color:#98c379">0</span> issue &#34;<span style="color:#e5c07b">letsencrypt.org</span><span style="color:#7f848e">;accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1968958296&#34;</span>
</span></span></code></pre></div><p>This record is placed on <code>olivine.joly.eu.org</code>, where a <code>A</code> (or <code>AAAA</code>) record contains the IP address of the Olivine server. In turn, the RSS reader host is a <code>CNAME</code> pointing to that <code>A</code> record.</p>
<h3 id="testing">Testing</h3>
<p>To test that the new setup works, we can try to issue a certificate for a new domain.
Just add it to the Caddy configuration as usual, for instance like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-caddy" data-lang="caddy"><span style="display:flex;"><span>test-caa.cj.rs {
</span></span><span style="display:flex;"><span>  <span style="color:#c678dd">respond</span> <span style="color:#98c379">&#34;Hello, world!&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Caddy logs should show that the certificate was successfully issued:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;level&#34;</span>:<span style="color:#98c379">&#34;info&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7f848e">// ...
</span></span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;msg&#34;</span>:<span style="color:#98c379">&#34;certificate obtained successfully&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;identifier&#34;</span>:<span style="color:#98c379">&#34;test-caa.cj.rs&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>You can similarly test that changing the <code>accounturi</code> in the <code>CAA</code>.
After the various caches expire, attempts to issue a certificate fail:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;level&#34;</span>:<span style="color:#98c379">&#34;error&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#7f848e">// ...
</span></span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;msg&#34;</span>:<span style="color:#98c379">&#34;could not get certificate from issuer&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">&#34;error&#34;</span>:<span style="color:#98c379">&#34;HTTP 0 urn:ietf:params:acme:error:caa - During secondary validation: While processing CAA for test-caa.cj.rs: CAA record for olivine.joly.eu.org prevents issuance&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><h2 id="just-a-mitigation-in-the-end">Just a Mitigation in the End</h2>
<p>We have limited the certificates that can be issued to a particular CA and to a particular account.
In turn, that account is tied to a private key stored on the server.</p>
<p>However, as <a href="https://www.devever.net/~hl/xmpp-incident#narrow">Hugo Landau explains</a>, this is only a mitigation.
A well resourced attacker could still succeed.
For instance, they could alter the <code>CAA</code> record read by the CA.</p>
<p>Thus, complementary defenses include monitoring certificate transparency to spot suspicious issuance.
Cloudflare <a href="https://blog.cloudflare.com/introducing-certificate-transparency-monitoring/">offers such a service</a>.
So does <a href="https://crt.sh">crt.sh</a>.
While CAs sometimes emit certificates without logging them to Certificate Transparency<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, those certificates should be rejected by most browsers.
In our case, with web services, that would be enough.
Other clients communicating over TLS might not reject them though, like XMPP clients of the <code>jabber.ru</code> example.</p>
<p>Despite the limitations of this <code>CAA</code>-based approach, it was interesting to learn more about it and see how the various pieces fit together.
I hope you enjoyed reading!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>At least that’s the basic domain validation. Other types exist, but they are not necessarily that much more reliable in practice.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>There is little the certificate authority can do, it’s not really their fault if someone manipulates traffic between them and the server to verify. They do use <a href="https://letsencrypt.org/2020/02/19/multi-perspective-validation/">multi-perspective validation</a> as mitigation measure though.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>That same folder also holds the private key used to sign in with the account.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>A customer can request that, if they want to avoid publishing a domain name in a public log.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Cargo Info in Neovim, or How Simple Features Go a Long Way</title><link>https://cj.rs/blog/cargo-info-in-neovim/</link><pubDate>Mon, 21 Oct 2024 22:39:34 +0100</pubDate><guid>https://cj.rs/blog/cargo-info-in-neovim/</guid><description>Integrate Vim and Cargo info with just a few lines of Lua using keywordprg.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>Open <code>cargo info</code> in Vim or neovim for the package under the cursor using <a href="#introducing-keywordprg">these 4 lines of Lua</a>.</p>
  </div>



<h2 id="cargo-info">Cargo info</h2>
<p><a href="https://blog.rust-lang.org/2024/10/17/Rust-1.82.0.html#whats-in-1820-stable">Rust 1.82</a> was released a couple of days ago.
It’s packed with improvements, but one in particular caught my eye.
Cargo now has a <code>info</code> sub-command.
It displays details about a package in the registry from the comfort of your terminal.
Here is an example:</p>
<pre tabindex="0"><code>$ cargo info lazy_static
lazy_static #macro #lazy #static
A macro for declaring lazily evaluated statics in Rust.
version: 1.5.0
license: MIT OR Apache-2.0
rust-version: unknown
documentation: https://docs.rs/lazy_static
repository: https://github.com/rust-lang-nursery/lazy-static.rs
crates.io: https://crates.io/crates/lazy_static/1.5.0
features:
  spin        = [dep:spin]
  spin_no_std = [spin]
note: to see how you depend on lazy_static, run `cargo tree --invert --package lazy_static@1.5.0`
</code></pre><p>Vim and neovim generally composes well with other terminal tools.
So how can we easily integrate <code>cargo info</code> and <code>neovim</code>?</p>
<h2 id="demo">Demo</h2>
<p>The goal is to press a key and display the <code>cargo info</code> for the crate under the cursor in a <code>Cargo.toml</code> file.
Like this:</p>
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/cargo-info-in-neovim/demo.json", document.getElementById('demo3'), {
"idleTimeLimit":  1 ,"poster": "npt:3","preload":  1 ,"speed": "1.5",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/blog/cargo-info-in-neovim/demo.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<h2 id="introducing-keywordprg">Introducing <code>keywordprg</code></h2>
<p>In the above demonstration, we press <a href="https://neovim.io/doc/user/various.html#K"><code>K</code></a> over a crates name. Then neovim executes the program set in <a href="https://neovim.io/doc/user/options.html#'keywordprg'"><code>keywordprg</code></a>, appending the work under the cursor. So if the cursor is on <code>lazy_static</code></p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-toml" data-lang="toml"><span style="display:flex;"><span>[<span style="color:#e06c75">dev-dependencies</span>]
</span></span><span style="display:flex;"><span><span style="color:#e06c75">iai</span> = <span style="color:#98c379">&#34;0.1&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">insta</span> = <span style="color:#98c379">&#34;1.40.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">lazy_static</span> = <span style="color:#98c379">&#34;1.5.0&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">#   ^ cursor is here when we press K</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">mktemp</span> = <span style="color:#98c379">&#34;0.5&#34;</span>
</span></span></code></pre></div><p>and <code>keywordprg</code> is set to <code>cargo info</code>, the command <code>cargo info lazy_static</code> is executed in a new terminal.</p>
<p>We can further improve this.
Since we want a fast answer (without checking the remote repository), let’s use <code>cargo info --offline</code>.
And we would like pretty colors, let’s force that using <code>--color=always</code>.</p>
<p>We only want to alter <code>keywordprg</code> when a <code>Cargo.toml</code> file is edited, because it makes no sense to call <code>cargo info</code> in a Python file.
We can use a <a href="https://neovim.io/doc/user/usr_41.html#ftplugin"><code>ftplugin</code></a> for toml files for that<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, so that the plugin is only loaded and executed when a toml file is executed.
Furthermore we can change the setting only when opening a file named “Cargo.toml”, instead of changing it for every toml file.
We can put the necessary configuration in <code>~/.config/nvim/ftplugin/toml.lua</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">vim.endswith</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">bufname</span>(), <span style="color:#98c379">&#34;Cargo.toml&#34;</span>) <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span>  <span style="color:#e06c75">vim.opt_local</span>.<span style="color:#e06c75">keywordprg</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;cargo info --color=always --offline&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">-- Add chars that are often part of keys, especially in rust crates</span>
</span></span><span style="display:flex;"><span><span style="color:#7f848e">-- (https://toml.io/en/v1.0.0#keys)</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">vim.opt_local</span>.<span style="color:#e06c75">iskeyword</span>:<span style="color:#e06c75">append</span> <span style="color:#98c379">&#34;-&#34;</span>
</span></span></code></pre></div><p>The Lua code above also changes the <a href="https://neovim.io/doc/user/options.html#'iskeyword'"><code>iskeyword</code></a> preference.
This is so that <code>tokio-test</code> is considered as one word (a “keyword” in Vim parlance), instead of two (<code>tokio</code> and <code>test</code>).
With this set, doing <code>K</code> on <code>tokio-test</code> will behave properly, because <code>tokio-test</code> is passed to the command, instead of just <code>tokio</code>.</p>
<p>One final note: the above configuration was the only thing sourced in the demo.
All the rest is default, vanilla, neovim 0.10.2.
And this does not even require recent neovim features, it has been supported in Vim for many years.</p>
<h2 id="learnings">Learnings</h2>
<p>Obviously this could be further refined.
Other options could be passed to <code>cargo info</code>, like <code>-v</code> to make it more verbose and list dependencies.
Some more scripting could add useful features to further improve the integration.
For this particular problem, there are even <a href="https://github.com/saecki/crates.nvim">a full plugin</a> with many more features.
But the point is to showcase a simple and robust integration<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, applicable in a wide variety of contexts.
In particular when the use case is too niche, a full plugin is too costly to write and maintain for the benefits it provides.</p>
<p>Let’s highlight the takeaways from this post that are applicable in a wide variety of contexts.</p>
<p>First, a number of Neovim commands can do something with the keyword under the cursor.
Adjusting what counts as a keyword boundary is often, but not always, done in the syntax file of a particular language.
Or you may just want to make a different trade off and set your own <code>iskeyword</code> value.</p>
<p>Second, the <a href="https://neovim.io/doc/user/various.html#K"><code>K</code></a> mapping is often used to look up a word in a documentation (it is set to open man pages by default).
It even works in visual mode, looking up the selected text.
And arbitrary Vim or shell commands can be used, instead of the default <code>:Man</code>.</p>
<p>Please do read the corresponding parts of the Vim manual linked in this section to learn more.
Next time, you may come up with your own quick integration between Vim and another tool.
Happy hacking!</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2024-10-22: Clarify why we use nvim settings instead of a plugin like <a href="https://github.com/saecki/crates.nvim">crates.nvim</a></p>
  </div>



<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>To keep the example simple, we don’t clean-up the local preferences that are set here. See the <a href="https://neovim.io/doc/user/usr_41.html#ftplugin">documentation</a> for best practices when sharing ftplugins.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>It’s also possible to do this in Vimscript instead of Lua.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Thanks to <a href="https://fosstodon.org/@dpom">@dpom@fosstodon.org</a> <a href="https://fosstodon.org/@dpom/113347670141692005">for pointing out</a> that crates.nvim has that feature already.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Link Aggregator Infrastructure</title><link>https://cj.rs/blog/link-aggregator-infrastructure/</link><pubDate>Thu, 21 Mar 2024 14:40:33 +0000</pubDate><guid>https://cj.rs/blog/link-aggregator-infrastructure/</guid><description>A look at the surprisingly simple infrastructure of link aggregators like HackerNews or Lobste.rs.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-warning">
    <p class="alert-heading">
      ⚠️
      
        DISCLAIMER
      
    </p>
    <p>This is not a comment on Reddit’s future profitability or any reflection on the stock performance.
This is a technical discussion on how a relatively simple link-aggreggator can be hosted, with the trade-offs.</p>
  </div>



<p>Reddit went public today.
Its <a href="https://www.sec.gov/Archives/edgar/data/1713445/000162828024011448/reddit-sx1a2.htm#i1b9a579e78a34dfa99f7f26daeec195b_40">IPO document</a> states<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> that they use AWS and GCP.
But I cannot find a break-down of infrastructure costs separately.</p>
<p>That makes me wonder: what does the infrastructure costs of a pure link-aggreggator look like?</p>
<p>I’ll go over two sites aggregating links related to computing, <a href="https://lobste.rs/">Lobsters</a> and <a href="https://news.ycombinator.com">Hacker News</a>.
They are relatively high-traffic websites.
<a href="https://news.ycombinator.com/item?id=39137882">Sites</a> <a href="https://news.ycombinator.com/item?id=39192941">popular</a> on Hacker News <a href="https://news.ycombinator.com/item?id=39746350">in particular</a> <a href="https://news.ycombinator.com/item?id=39631607">often</a> <a href="https://news.ycombinator.com/item?id=39536126">go down</a> <a href="https://news.ycombinator.com/item?id=39419248">due to</a> <a href="https://news.ycombinator.com/item?id=39224966">the sudden</a> <a href="https://news.ycombinator.com/item?id=39137882">popularity</a><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
It’s a <a href="https://lobste.rs/search?q=lobstered&amp;what=comments&amp;order=newest">little less common</a> with Lobsters, but it happens there as well.</p>
<h2 id="lobsters">Lobste.rs</h2>
<p><a href="https://lobste.rs/">Lobsters</a> is very transparent on its infrastructure:</p>




  <figure>
    <blockquote cite="https://lobste.rs/about">
      <p>Lobsters is hosted on three VPSs at DigitalOcean: a s-4vcpu-8gb for the web server, a s-2vcpu-4gb for the mariadb server, and a s-1vcpu-1gb for the IRC bot
[…]  we use restic for backups to b2</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://lobste.rs/about">https://lobste.rs/about</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Using <a href="https://slugs.do-api.dev/">public prices</a>, those servers cost $78 ($48+$24+$6) a month to run. Obviously, there are other costs for monitoring, backups, managed DNS<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>…</p>
<p>I’ve <a href="https://lobste.rs/s/qpwghe">requested some numbers</a> on the load the site is facing and infrastructure utilization.
I’ll update that blog post with the results.</p>
<h2 id="hacker-news">Hacker News</h2>
<p>It’s harder to find details on <a href="https://news.ycombinator.com">Hacker News’s</a> infrastructure, but the moderator of the site answered questions about the infra in <a href="https://news.ycombinator.com/item?id=28478379">this thread</a><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>:</p>




  <figure>
    <blockquote cite="https://news.ycombinator.com/item?id=16076041">
      <p>We’re recently running two machines (master and standby) at M5 Hosting. All of HN runs on a single box, nothing exotic:</p>
<pre><code>  CPU: Intel(R) Xeon(R) CPU E5-2637 v4 @ 3.50GHz (3500.07-MHz K8-class CPU)
  FreeBSD/SMP: 2 package(s) x 4 core(s) x 2 hardware threads
  Mirrored SSDs for data, mirrored magnetic for logs (UFS)
</code></pre>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://news.ycombinator.com/item?id=16076041">https://news.ycombinator.com/item?id=16076041</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>







  <figure>
    <blockquote cite="https://news.ycombinator.com/item?id=28479595">
      <p>Number of daily requests has gone up closer to 6M</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://news.ycombinator.com/item?id=28479595">https://news.ycombinator.com/item?id=28479595</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>







  <figure>
    <blockquote cite="https://news.ycombinator.com/item?id=28496642">
      <p>We use an Nginx front end for that [caching]. It all runs on the same box though.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://news.ycombinator.com/item?id=28496642">https://news.ycombinator.com/item?id=28496642</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>So a <a href="https://ark.intel.com/content/www/us/en/ark/products/92983/intel-xeon-processor-e5-2637-v4-15m-cache-3-50-ghz.html">CPU from 2016</a> handles the load for a very popular site.</p>
<p>No CDN either, requests are made to <code>news.ycombinator</code>, with DNS records pointing to M5 Hosting IPs (<code>2606:7100:1:67::26</code> and <code>209.216.230.207</code>)<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<h2 id="conclusions">Conclusions</h2>
<p>Those two popular link-aggregator offer to submit and comment on links, two historical features of Reddit.
Of course now Reddit also has chat, image hosting and presumably a bunch of other features.
It also has <a href="https://www.sec.gov/Archives/edgar/data/1713445/000162828024011448/reddit-sx1a2.htm#i1b9a579e78a34dfa99f7f26daeec195b_40">“73.1 million daily active uniques (“DAUq”), around the world”</a>, probably orders of magnitude more than Hacker News.
Finally, Reddit might<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> theoretically have higher uptime, because <a href="https://news.ycombinator.com/item?id=35334292">Hacker News</a> and <a href="https://lobste.rs/s/whbyxt/2023_08_30_outage_postmortem">Lobsters</a> sometimes have their single-point-of-failure fail, for instance during an upgrade.</p>
<p>After those incidents though, users tend to post understanding comments, pointing out that they thought their Internet connection was faulty, before thinking that the aggregator could be down.
This sort of simple hosting might be the right trade-off for a non-profit link-aggregator: simple, so rarely down due to an operational mistake and with base components simple enough that users are understanding when an outage happens.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>“Currently our cloud service infrastructure is run on our cloud services providers (“CSPs”), which are currently Amazon Web Services and Google Cloud Platform”.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>And those links are only from what I could <a href="https://hn.algolia.com/?dateRange=pastYear&amp;page=0&amp;prefix=true&amp;query=hug%20of%20death&amp;sort=byDate&amp;type=comment">quickly find</a> in comments from the last two month.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>They don’t mention using a CDN on Lobsters.
It’s consistent with what I observe loading the homepage, where all requests are made to the host <code>lobest.rs</code>. A quick DNS lookup returns <code>67.205.189.7</code> and <code>2604:a880:400:d0::2082:1001</code>, and both IPs are owned by DigitalOcean<sup id="fnref1:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>These details are consistent with this <a href="https://news.ycombinator.com/item?id=35334292">comment</a> from 2023.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>According to <a href="https://ipinfo.io/">ipinfo</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>It’s hard to find reliable uptime data for the 3 sites and compare.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>New Asciinema Hugo Module Versions</title><link>https://cj.rs/blog/new-asciinema-hugo-module-versions/</link><pubDate>Thu, 07 Mar 2024 07:35:45 +0000</pubDate><guid>https://cj.rs/blog/new-asciinema-hugo-module-versions/</guid><description>&lt;p&gt;I’ve just added the last 8 versions of the &lt;a href="https://github.com/asciinema/asciinema-player/"&gt;Asciinema player&lt;/a&gt; to &lt;a href="https://cj.rs/gohugo-asciinema/"&gt;gohugo-asciinema&lt;/a&gt;, my &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; module to easily integrate the Asciinema player to your website.
Lots of exciting new features, like improved decoding and rendering efficiency as well as some support for streaming asciicasts.
See the &lt;a href="#changelogs"&gt;changelog links below&lt;/a&gt; for details of the changes.
There were no changes made to the Hugo module itself, besides copying the JS &amp;amp; CSS from the Asciinema player.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I’ve just added the last 8 versions of the <a href="https://github.com/asciinema/asciinema-player/">Asciinema player</a> to <a href="/gohugo-asciinema/">gohugo-asciinema</a>, my <a href="https://gohugo.io/">Hugo</a> module to easily integrate the Asciinema player to your website.
Lots of exciting new features, like improved decoding and rendering efficiency as well as some support for streaming asciicasts.
See the <a href="#changelogs">changelog links below</a> for details of the changes.
There were no changes made to the Hugo module itself, besides copying the JS &amp; CSS from the Asciinema player.</p>
<p>Security note: all the releases are <a href="/open-source/docs/security/">signed</a>.
You can even check that the files JS and CSS file in the plugin match the ones uploaded in release pages.
Thanks to <a href="https://github.com/ku1ik">@ku1ik</a> for providing these files directly!</p>
<h2 id="updating-in-hugo">Updating in Hugo</h2>
<p>As usual to get the latest version the player on your website, you can run:</p>
<pre tabindex="0"><code>hugo mod get -u cj.rs/gohugo-asciinema
</code></pre><h2 id="changelogs">Changelogs</h2>
<p>Here is the changelogs in the upstream Asciinema player for the updated versions:</p>
<ul>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.7.0">3.7.0</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.6.4">3.6.4</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.6.3">3.6.3</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.6.2">3.6.2</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.6.1">3.6.1</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.5.0">3.5.0</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.4.0">3.4.0</a></li>
<li><a href="https://github.com/asciinema/asciinema-player/releases/tag/v3.3.0">3.3.0</a></li>
</ul>
<h2 id="delay-updating-the-css-and-js-files">Delay Updating the CSS and JS Files</h2>
<p>I apologize for the delay in updating those versions in my Hugo module.
I do have alerts set up when a new version comes in but I’m still testing manually to make sure that the updated version works well.
This Hugo module being a side-project, I did not get the chance to do the testing any earlier.
I’ve tested all those versions against various pages of my website, and it all worked well.
But please feel free to <a href="https://github.com/cljoly/gohugo-asciinema/issues/new/choose">report</a> any problem you may encounter.</p>
<p>Moving forward, I’ll rely <a href="https://github.com/cljoly/gohugo-asciinema/blob/abb45e1c71409f195ab1d25d20bd548f12291e0c/update.fish">on a script</a> to automate the bulk of the update, that should help updating only a few days after the release.
That script is not perfect and will be improved incrementally.</p>
<p>Since this is a release related to Asciinema, here is an asciicast of that new script downloading the latest CSS and JS, making a commit and tagging it.</p>
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("./update.json", document.getElementById('demo3'), {
"poster": "npt:0:05",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/update.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<p>Please feel free to use this script and open a PR if you spot an update before I do in the future 😁.</p>
]]></content:encoded></item><item><title>My Commits and Tags Are Now Signed</title><link>https://cj.rs/blog/my-commits-and-tags-are-now-signed/</link><pubDate>Fri, 29 Dec 2023 22:36:01 +0000</pubDate><guid>https://cj.rs/blog/my-commits-and-tags-are-now-signed/</guid><description>&lt;h2 id="announcement"&gt;Announcement&lt;/h2&gt;
&lt;p&gt;I’m now signing my git commit and tags with an SSH key.
Details of the fingerprint can be found in the &lt;a href="https://cj.rs/open-source/docs/security/#signature"&gt;security document&lt;/a&gt;.
It says that commit after 2024-01-01 are going to be signed, because I’m starting now on one machine and I will propagate the configuration over the next few days to other machines.&lt;/p&gt;
&lt;h2 id="why"&gt;Why&lt;/h2&gt;
&lt;p&gt;Why bother with cryptographic signatures? &lt;br&gt;
Anyone can pretend to be me.
They just need to write my name and email in the author fields of a commit message.
However&lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;, I’m the only one able to produce signatures with that particular public key.
This will help to check that I’m actually the author of the commits and tags you rely on when using my code.&lt;/p&gt;</description><content:encoded><![CDATA[<h2 id="announcement">Announcement</h2>
<p>I’m now signing my git commit and tags with an SSH key.
Details of the fingerprint can be found in the <a href="https://cj.rs/open-source/docs/security/#signature">security document</a>.
It says that commit after 2024-01-01 are going to be signed, because I’m starting now on one machine and I will propagate the configuration over the next few days to other machines.</p>
<h2 id="why">Why</h2>
<p>Why bother with cryptographic signatures? <br>
Anyone can pretend to be me.
They just need to write my name and email in the author fields of a commit message.
However<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I’m the only one able to produce signatures with that particular public key.
This will help to check that I’m actually the author of the commits and tags you rely on when using my code.</p>
<p>I’m doing it only now because GPG <a href="https://words.filippo.io/giving-up-on-long-term-pgp/">can be quite hard to use</a>, especially with multiple machines.
So I had to wait for the SSH signing scheme in Git to be supported more widely.</p>
<h2 id="verification">Verification</h2>
<h3 id="code-hosting-platforms">Code Hosting Platforms</h3>
<p>GitHub will check this, adding a “verified” badge like this:</p>
<figure class="align-center ">
    <img loading="lazy" src="./signed-commit.webp#center"
         alt="https://github.com/cljoly/.github/commit/4df602d7a8689676c5d907b8f0fc84f60970496e" width="95%"/> <figcaption>
            When viewing a signed commit on GitHub<p><a href="https://github.com/cljoly/.github/commit/4df602d7a8689676c5d907b8f0fc84f60970496e">https://github.com/cljoly/.github/commit/4df602d7a8689676c5d907b8f0fc84f60970496e</a></p>
        </figcaption>
</figure>

<figure>
    <img loading="lazy" src="./commit-list.webp"/> <figcaption>
            Signed commits in the commit log on GitHub.
        </figcaption>
</figure>

<p>Codeberg shows similar badges.</p>
<h3 id="locally">Locally</h3>
<p>Signatures can of course also be verified locally.
This <a href="https://calebhearth.com/sign-git-with-ssh">blog post</a> explains in details how to do it. <br>
<em>TL;DR</em>: populate a file (<code>allowed_signers</code>) with trusted keys, configures git to use it and then commands like <code>git log --show-signature</code> will check that signatures are valid for each commit.</p>
<h2 id="caveats">Caveats</h2>
<p>I wrote earlier that I was the only one able to generate this signature for this particular public key.
This is true only as long as the corresponding secret key remains secret.
I’m using a strong password to encrypt that key on my disk and that password is accessible only by physically touching a <a href="https://www.yubico.com/products/yubikey-5-overview/">Yubikey</a>.
This goes a long way towards preventing the private key leaking.
However, it is still possible for an attacker taking full control of my machine for extended periods of time to intercept that password and to decipher the secret key with it.
Then, they will be able to produce signatures as if they were me.
It would be a bit harder for the attacker if the SSH key was on the Yubikey, but then it becomes tricky to work on multiple machines.
So that’s the setup for now, a compromise between potentially higher level of security and usability so that I actually use it.</p>
<p>Currently, I’m not signing release artifacts, but I might in the future.
GPG is more common for this, but <a href="https://words.filippo.io/giving-up-on-long-term-pgp/">it’s very hard to maintain long term GPG keys</a>.
With this <code>allowed_signers</code> SSH file, rotating keys seems easier, so I’m more likely to do it more often and limit risks.
I’ll try with only commit and tag signing first.
Then I’ll apply the learnings to sign release artefacts.</p>
<p><em>EDIT</em>(2023-12-31): This <a href="https://lobi.to/writes/wacksigning/">blog post</a> describes the various options to sign commits very well, in particular talking about revocation scenarios. It was written shortly after I wrote this piece. I mostly agree with the author, but I think that SSH is a good middle ground: revocation works very well locally, it can be paired with Yubikey verification and it’s already supported by code hosting providers, without pesky OpenID verifications like gitsign.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>See the <a href="https://cj.rs/blog/#caveats">Caveats</a> at the end of this post.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Luasnip and Treesitter for Smarter Snippets</title><link>https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/</link><pubDate>Wed, 30 Aug 2023 13:28:14 +0100</pubDate><guid>https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/</guid><description>An example on Go error handling.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>A context-aware snippet for Go error handling code, returning the right types, with the default values.</p>
<ul>
<li><a href="#demo">Demo</a></li>
<li><a href="#code">Snippet</a></li>
</ul>
  </div>







  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>TJ DeVries made a <a href="https://www.youtube.com/watch?v=aNWx-ym7jjI">video</a> explaining an improved version of this snippet. You may still find the discussion on Go errors in this post interesting.</p>
  </div>



<h2 id="introduction">Introduction</h2>
<p><a href="https://go.dev">Golang’s</a> error handling is notoriously verbose. It was also the top pain point in the <a href="https://go.dev/blog/survey2022-q2-results">Go Developer Survey Q2 2022</a>. <a href="https://github.com/golang/go/issues/56165">Numerous</a> <a href="https://github.com/golang/go/issues/32437">proposals</a> <a href="https://github.com/golang/go/issues/37141">to simplify</a> error handling have been written, but at the time of writing, none have been accepted.</p>
<p>So we have to live with the current state of things. It’s ok, Neovim with Treesitter has a good “understanding” of code, so we can use it to generate the error handling code.</p>
<h3 id="treesitter">Treesitter</h3>
<p>One of <a href="https://github.com/neovim/neovim/releases/tag/v0.5.0">Neovim 0.5</a> most exciting features was the introduction of Treesitter.
This new parsing system gives the editor a basic understanding of the code at hand: the editor “knows” what the function name is, where a variable is defined…
It enables great plugins, <a href="https://github.com/SmiteshP/nvim-gps">showing the context</a> around some code, <a href="https://github.com/nvim-treesitter/nvim-treesitter-textobjects">improved selection</a>, <a href="https://github.com/nvim-treesitter/nvim-treesitter-refactor">better renaming and navigation</a> and more!
In this post, we will look into how Treesitter can be combined with <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> for smarter snippets.</p>
<h2 id="error-handling-in-go">Error Handling in Go</h2>




  
  
  
  

  <div class="alert alert-note">
    <p class="alert-heading">
      ℹ️
      
        Note
      
    </p>
    <p>Feel free to <a href="#clever">skip</a> this section if you already know about Go error handling.</p>
  </div>



<p>In <a href="https://go.dev">Golang</a>, errors are handled by returning a value of type <code>error</code>, sometimes along the types returned when everything goes well. For instance, the <a href="https://pkg.go.dev/time#Parse"><code>time.Parse</code></a> function has the following signature:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#e06c75">layout</span>, <span style="color:#e06c75">value</span> <span style="color:#e5c07b">string</span>) (<span style="color:#e06c75">Time</span>, <span style="color:#e5c07b">error</span>)
</span></span></code></pre></div><p><code>time.Parse</code> will try to parse a string with the given format.
If it’s successful, it’ll return a value of type <code>Time</code> and the error will be <code>nil</code>.
If a date can’t be parsed, then the error won’t be <code>nil</code>.
And so it’s common in Go to check for errors like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">timeStr</span> <span style="color:#56b6c2">:=</span> <span style="color:#98c379">&#34;Aug 6, 2022 @ 7:54pm&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#e06c75">t</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#e06c75">time</span>.<span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#98c379">&#34;Jan 2, 2006 @ 3:04pm&#34;</span>, <span style="color:#e06c75">timeStr</span>)
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">log</span>.<span style="color:#61afef;font-weight:bold">Fatal</span>(<span style="color:#e06c75">err</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span><span style="color:#7f848e">// Now, we can safely use t</span>
</span></span></code></pre></div><p>In the example above, we just log the error and panic. But if we are in a function, we will likely return an error to the caller, wrapping the error returned by <code>time.Parse</code>. For instance:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-go" data-lang="go"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">1</span><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">parseLong</span>(<span style="color:#e06c75">timeStr</span> <span style="color:#e5c07b">string</span>) (<span style="color:#e06c75">Time</span>, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">2</span><span>    <span style="color:#e06c75">t</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#e06c75">time</span>.<span style="color:#61afef;font-weight:bold">Parse</span>(<span style="color:#98c379">&#34;Jan 2, 2006 @ 3:04pm&#34;</span>, <span style="color:#e06c75">timeStr</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">3</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">4</span><span>        <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;couldn’t parse %v: %v&#34;</span>, <span style="color:#e06c75">timeStr</span>, <span style="color:#e06c75">err</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">5</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">6</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>, <span style="color:#e5c07b">nil</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">7</span><span>}
</span></span></code></pre></div><p>The key takeaway here is that the <code>return</code> line in the error case (line 4 above) depends on the return type of the function and so can vary significantly.</p>
<p>This is covered in more details in this <a href="https://go.dev/blog/error-handling-and-go">post</a> on the Go blog.</p>
<h2 id="clever">Clever Snippets with LuaSnip and Treesitter</h2>
<p>As we have seen, error handling code varies depending on the number and type of the values returned by the parent function.
So we are very often writing variations of the following in our Go code:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">f</span>(<span style="color:#e06c75">arg1</span> <span style="color:#e5c07b">string</span>, <span style="color:#e06c75">arg2</span> <span style="color:#e5c07b">string</span>) (…, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">someFunction</span>(<span style="color:#e06c75">arg1</span>, <span style="color:#e06c75">arg2</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#c678dd">return</span> …, <span style="color:#e06c75">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    …
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Or with 3 return values:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">f</span>(<span style="color:#e06c75">arg</span> <span style="color:#e5c07b">string</span>) (…, …, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">someFunction</span>(<span style="color:#e06c75">arg</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#c678dd">return</span> …, …, <span style="color:#e06c75">err</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>    …
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It would be handy to have a snippet that would adjust the <code>return</code>’s shape depending on the return type of the function that contains it.</p>
<h3 id="demo">Demo</h3>
<p>Here is a short asciicast demonstration<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> of the snippet we are going to build. It is called <code>smart_err</code>:</p>

    <script src="https://cj.rs/js/asciinema-player.min.0a355ec0e0db21622c1aa829f4ffd49e.js"></script>
    <link rel="stylesheet" type="text/css" href="https://cj.rs/css/asciinema-player.min.b76f6c28744857ba1315b54d09acafff.css" /><div id="demo1"></div>
<script>
AsciinemaPlayer.create("/blog/luasnip-and-treesitter-for-smarter-snippets/demo.json", document.getElementById('demo1'), {
"idleTimeLimit":  0.4 ,"loop":  true ,"poster": "npt:0:31","speed":  1.2 ,
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/blog/luasnip-and-treesitter-for-smarter-snippets/demo.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<p>The snippet inserted (line 11 in the asciicast) is</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">myVal</span>, <span style="color:#e06c75">myErr</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">anotherFunc</span>(<span style="color:#e06c75">arg1</span>, <span style="color:#e06c75">arg2</span>)
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">myErr</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;anotherFunc: %v&#34;</span>, <span style="color:#e06c75">myErr</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>when the return type of the function was <code>(bool, error)</code>.</p>
<p>And</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#e06c75">val</span>, <span style="color:#e06c75">err</span> <span style="color:#56b6c2">:=</span> <span style="color:#61afef;font-weight:bold">f</span>()
</span></span><span style="display:flex;"><span><span style="color:#c678dd">if</span> <span style="color:#e06c75">err</span> <span style="color:#56b6c2">!=</span> <span style="color:#e5c07b">nil</span> {
</span></span><span style="display:flex;"><span>	<span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;f: %v&#34;</span>, <span style="color:#e06c75">err</span>), <span style="color:#d19a66">0</span>, <span style="color:#98c379">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>when the return type was <code>(bool, error, int, string)</code>.</p>
<p>And it even works in nested functions:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">func</span> <span style="color:#61afef;font-weight:bold">myFunc</span>() (<span style="color:#56b6c2">*</span><span style="color:#e06c75">MyStruct</span>, <span style="color:#e5c07b">error</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#7f848e">// ...</span>
</span></span><span style="display:flex;"><span>	<span style="color:#e06c75">a</span> <span style="color:#56b6c2">:=</span> <span style="color:#c678dd">func</span>() (<span style="color:#e5c07b">bool</span>, <span style="color:#e5c07b">error</span>, <span style="color:#e5c07b">int</span>, <span style="color:#e5c07b">string</span>) {
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">// The snippet detected that we need 4 values here,</span>
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">// and not 2 as in the outer function</span>
</span></span><span style="display:flex;"><span>		<span style="color:#c678dd">return</span> <span style="color:#e06c75">v</span>, <span style="color:#e5c07b">nil</span>, <span style="color:#d19a66">0</span>, <span style="color:#98c379">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>	}
</span></span></code></pre></div><p>Also note that we can choose to wrap the containing error, or not, with a keystroke (bound to <code>require(&quot;luasnip&quot;).change_choice(1)</code>). The snippet switches between the following 3 forms (line 18 in the asciicast):</p>
<ol>
<li>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">err</span>
</span></span></code></pre></div></li>
<li>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">fmt</span>.<span style="color:#61afef;font-weight:bold">Errorf</span>(<span style="color:#98c379">&#34;f: %v&#34;</span>, <span style="color:#e06c75">err</span>)
</span></span></code></pre></div></li>
<li>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#c678dd">return</span> <span style="color:#e5c07b">false</span>, <span style="color:#e06c75">errors</span>.<span style="color:#61afef;font-weight:bold">Wrap</span>(<span style="color:#e06c75">err</span>, <span style="color:#98c379">&#34;f&#34;</span>)
</span></span></code></pre></div></li>
</ol>
<p>That’s pretty cool! Let’s see how to add it to your Neovim configuration.</p>
<h3 id="code">Code</h3>
<p>The code to define this snippet was initially written by <a href="https://github.com/tjdevries">TJ DeVries</a><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. I’ve only slightly adapted it so that it fits in one file. I believe the author welcomes this<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>:</p>




  <figure>
    <blockquote cite="https://github.com/tjdevries/config_manager#readme">
      <p>This is TJ&rsquo;s configuration repo. Feel free to use whatever you would like from it! It&rsquo;d be great if you mentioned where it came from if you think it&rsquo;s cool.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://github.com/tjdevries/config_manager#readme">https://github.com/tjdevries/config_manager#readme</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Copy this alongside your other LuaSnip snippets, for instance in <a href="./go.lua"><code>~/.config/nvim/snippets/go.lua</code></a>:</p>
<div class="highlight" id="luasnip"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  1</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ls</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;luasnip&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  2</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">f</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.function_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  3</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">s</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.s</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  4</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">i</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.insert_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  5</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">t</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.text_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  6</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">d</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.dynamic_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  7</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">c</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.choice_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  8</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">snippet_from_nodes</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ls.sn</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">  9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 10</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ts_locals</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;nvim-treesitter.locals&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 11</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">ts_utils</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">require</span> <span style="color:#98c379">&#34;nvim-treesitter.ts_utils&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 12</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">get_node_text</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">get_node_text</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 14</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 15</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">same</span>(<span style="color:#e06c75">index</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 16</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 17</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">1</span>]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 18</span><span>  <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">index</span> })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 19</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 20</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 21</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 22</span><span><span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">query.set</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 23</span><span>  <span style="color:#98c379">&#34;go&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 24</span><span>  <span style="color:#98c379">&#34;LuaSnip_Result&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 25</span><span>  <span style="color:#98c379">[[ [
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 26</span><span><span style="color:#98c379">    (method_declaration result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 27</span><span><span style="color:#98c379">    (function_declaration result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 28</span><span><span style="color:#98c379">    (func_literal result: (_) @id)
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 29</span><span><span style="color:#98c379">  ] ]]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 30</span><span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 31</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 32</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 33</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">transform</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">text</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 34</span><span>  <span style="color:#c678dd">if</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;int&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 35</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;0&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 36</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;error&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 37</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">info</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 38</span><span>      <span style="color:#e06c75">info.index</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">info.index</span> <span style="color:#56b6c2">+</span> <span style="color:#d19a66">1</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 39</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 40</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">c</span>(<span style="color:#e06c75">info.index</span>, {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 41</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;fmt.Errorf(&#34;%s: %%v&#34;, %s)&#39;</span>, <span style="color:#e06c75">info.func_name</span>, <span style="color:#e06c75">info.err_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 42</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">info.err_name</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 43</span><span>        <span style="color:#7f848e">-- Be cautious with wrapping, it makes the error part of the API of the</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 44</span><span>        <span style="color:#7f848e">-- function, see https://go.dev/blog/go1.13-errors#whether-to-wrap</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 45</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;fmt.Errorf(&#34;%s: %%w&#34;, %s)&#39;</span>, <span style="color:#e06c75">info.func_name</span>, <span style="color:#e06c75">info.err_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 46</span><span>        <span style="color:#7f848e">-- Old style (pre 1.13, see https://go.dev/blog/go1.13-errors), using</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 47</span><span>        <span style="color:#7f848e">-- https://github.com/pkg/errors</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 48</span><span>        <span style="color:#e06c75">t</span>(<span style="color:#e06c75">string.format</span>(<span style="color:#98c379">&#39;errors.Wrap(%s, &#34;%s&#34;)&#39;</span>, <span style="color:#e06c75">info.err_name</span>, <span style="color:#e06c75">info.func_name</span>)),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 49</span><span>      })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 50</span><span>    <span style="color:#c678dd">else</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 51</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;err&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 52</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 53</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;bool&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 54</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;false&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 55</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;string&#34;</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 56</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#39;&#34;&#34;&#39;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 57</span><span>  <span style="color:#c678dd">elseif</span> <span style="color:#e06c75">string.find</span>(<span style="color:#e06c75">text</span>, <span style="color:#98c379">&#34;*&#34;</span>, <span style="color:#d19a66">1</span>, <span style="color:#e5c07b">true</span>) <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 58</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;nil&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 59</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 60</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 61</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">t</span>(<span style="color:#e06c75">text</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 62</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 63</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 64</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">handlers</span> <span style="color:#56b6c2">=</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 65</span><span>  [<span style="color:#98c379">&#34;parameter_list&#34;</span>] <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 66</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">result</span> <span style="color:#56b6c2">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 67</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 68</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">count</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">node</span>:<span style="color:#e06c75">named_child_count</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 69</span><span>    <span style="color:#c678dd">for</span> <span style="color:#e06c75">idx</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>, <span style="color:#e06c75">count</span> <span style="color:#56b6c2">-</span> <span style="color:#d19a66">1</span> <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 70</span><span>      <span style="color:#e06c75">table.insert</span>(<span style="color:#e06c75">result</span>, <span style="color:#e06c75">transform</span>(<span style="color:#e06c75">get_node_text</span>(<span style="color:#e06c75">node</span>:<span style="color:#e06c75">named_child</span>(<span style="color:#e06c75">idx</span>), <span style="color:#d19a66">0</span>), <span style="color:#e06c75">info</span>))
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 71</span><span>      <span style="color:#c678dd">if</span> <span style="color:#e06c75">idx</span> <span style="color:#56b6c2">~=</span> <span style="color:#e06c75">count</span> <span style="color:#56b6c2">-</span> <span style="color:#d19a66">1</span> <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 72</span><span>        <span style="color:#e06c75">table.insert</span>(<span style="color:#e06c75">result</span>, <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;, &#34;</span> })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 73</span><span>      <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 74</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 75</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 76</span><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">result</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 77</span><span>  <span style="color:#c678dd">end</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 78</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 79</span><span>  [<span style="color:#98c379">&#34;type_identifier&#34;</span>] <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 80</span><span>    <span style="color:#c678dd">local</span> <span style="color:#e06c75">text</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">get_node_text</span>(<span style="color:#e06c75">node</span>, <span style="color:#d19a66">0</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 81</span><span>    <span style="color:#c678dd">return</span> { <span style="color:#e06c75">transform</span>(<span style="color:#e06c75">text</span>, <span style="color:#e06c75">info</span>) }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 82</span><span>  <span style="color:#c678dd">end</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 83</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 84</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 85</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 86</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">go_result_type</span>(<span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 87</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">cursor_node</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ts_utils.get_node_at_cursor</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 88</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">scope</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">ts_locals.get_scope_tree</span>(<span style="color:#e06c75">cursor_node</span>, <span style="color:#d19a66">0</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 89</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 90</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">function_node</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 91</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">v</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">ipairs</span>(<span style="color:#e06c75">scope</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 92</span><span>    <span style="color:#c678dd">if</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 93</span><span>      <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;function_declaration&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 94</span><span>      <span style="color:#56b6c2">or</span> <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;method_declaration&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 95</span><span>      <span style="color:#56b6c2">or</span> <span style="color:#e06c75">v</span>:<span style="color:#e06c75">type</span>() <span style="color:#56b6c2">==</span> <span style="color:#98c379">&#34;func_literal&#34;</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 96</span><span>    <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 97</span><span>      <span style="color:#e06c75">function_node</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">v</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 98</span><span>      <span style="color:#c678dd">break</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 99</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">100</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">101</span><span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">102</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">query</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">vim.treesitter</span>.<span style="color:#e06c75">query.get</span>(<span style="color:#98c379">&#34;go&#34;</span>, <span style="color:#98c379">&#34;LuaSnip_Result&#34;</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">103</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">node</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">query</span>:<span style="color:#e06c75">iter_captures</span>(<span style="color:#e06c75">function_node</span>, <span style="color:#d19a66">0</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">104</span><span>    <span style="color:#c678dd">if</span> <span style="color:#e06c75">handlers</span>[<span style="color:#e06c75">node</span>:<span style="color:#e06c75">type</span>()] <span style="color:#c678dd">then</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">105</span><span>      <span style="color:#c678dd">return</span> <span style="color:#e06c75">handlers</span>[<span style="color:#e06c75">node</span>:<span style="color:#e06c75">type</span>()](<span style="color:#e06c75">node</span>, <span style="color:#e06c75">info</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">106</span><span>    <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">107</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">108</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">109</span><span>  <span style="color:#c678dd">return</span> { <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;nil&#34;</span> }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">110</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">111</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">112</span><span><span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">113</span><span><span style="color:#c678dd">local</span> <span style="color:#e06c75">go_ret_vals</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">114</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">snippet_from_nodes</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">115</span><span>    <span style="color:#e5c07b">nil</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">116</span><span>    <span style="color:#e06c75">go_result_type</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">117</span><span>      <span style="color:#e06c75">index</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">118</span><span>      <span style="color:#e06c75">err_name</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">1</span>][<span style="color:#d19a66">1</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">119</span><span>      <span style="color:#e06c75">func_name</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">args</span>[<span style="color:#d19a66">2</span>][<span style="color:#d19a66">1</span>],
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">120</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">121</span><span>  )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">122</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">123</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">124</span><span><span style="color:#c678dd">return</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">125</span><span>  <span style="color:#7f848e">-- Adapted from https://github.com/tjdevries/config_manager/blob/1a93f03dfe254b5332b176ae8ec926e69a5d9805/xdg_config/nvim/lua/tj/snips/ft/go.lua</span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">126</span><span>  <span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;smart_err&#34;</span>, {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">127</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">1</span>, { <span style="color:#98c379">&#34;val&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">128</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;, &#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">129</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">2</span>, { <span style="color:#98c379">&#34;err&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">130</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34; := &#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">131</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">3</span>, { <span style="color:#98c379">&#34;f&#34;</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">132</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;(&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">133</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">4</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">134</span><span>    <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;)&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">135</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#98c379">&#34;if &#34;</span> },
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">136</span><span>    <span style="color:#e06c75">same</span>(<span style="color:#d19a66">2</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">137</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34; != nil {&#34;</span>, <span style="color:#98c379">&#34;</span><span style="color:#98c379">\t</span><span style="color:#98c379">return &#34;</span> },
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">138</span><span>    <span style="color:#e06c75">d</span>(<span style="color:#d19a66">5</span>, <span style="color:#e06c75">go_ret_vals</span>, { <span style="color:#d19a66">2</span>, <span style="color:#d19a66">3</span> }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">139</span><span>    <span style="color:#e06c75">t</span> { <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#98c379">&#34;}&#34;</span> },
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">140</span><span>    <span style="color:#e06c75">i</span>(<span style="color:#d19a66">0</span>),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">141</span><span>  }),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">142</span><span>}
</span></span></code></pre></div><p>We start by importing and aliasing some LuaSnip functions. The snippet itself is defined at the end (line 126). Most of those functions were covered in the <a href="https://cj.rs/blog/generating-snippets-with-luasnip/#3-generating-snippets-variants">previous post</a>, see there for calls to <code>s</code>, <code>i</code> and <code>t</code>. The clever part of this snippet is the <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#dynamicnode">dynamic node</a> <code>d</code> on line 138. There, we call a function that queries (line 22 and 102) Treesitter to find the return types of the current function in Go code. Depending on the types found, default values for the type are used (line 33).</p>
<h3 id="treesitter-query">Treesitter Query</h3>
<p>The Treesitter query is where the magic happens, so let’s take a deeper look.</p>
<p>Treesitter parses code and builds a tree with node for various parts of the code. Each node can have children, like a function definition has a name, a body… This tree can be queried in a Lisp-like language (s-expressions). For instance, in our snippet definition, we use this query:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">1</span><span>[
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">2</span><span>    (<span style="color:#61afef;font-weight:bold">method_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">3</span><span>    (<span style="color:#61afef;font-weight:bold">function_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">4</span><span>    (<span style="color:#61afef;font-weight:bold">func_literal</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id</span>)
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">5</span><span>]
</span></span></code></pre></div><p>The <a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">bracket syntax</a> returns the nodes that match any of the patterns inside the brackets. So it’s effectively the union of all the nodes matched by any of the 3 patterns (on lines 2 to 4). Let’s take a concrete example and put the <a href="https://gist.github.com/cljoly/0b754259df036ba0163f2cdaad48e048#file-main-go">Go code</a> in the <a href="https://tree-sitter.github.io/tree-sitter/playground">Treesitter playground</a>, with a trimmed down version of the query:</p>
<p>
<img alt="The Treesitter playground with the example Go code above inserted at the top, the query in the middle and the tree at the bottom" loading="lazy" src="/blog/luasnip-and-treesitter-for-smarter-snippets/treesitter-playground.png"></p>
<p>The tree built by Treesitter is at the bottom, with the corresponding code at the top and the query in the middle. The <code>function_declaration</code> node is highlighted in gray in the tree, like the corresponding part of the code (from line 13 to line 22). This node has a number of children, with the function <code>name</code>, the <code>parameters</code> of the function, a <code>result</code> node for the return type and <code>body</code> for the body of the function between curly braces.</p>
<p>Taking the first pattern on the playground, slightly modified:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span>    (<span style="color:#61afef;font-weight:bold">function_declaration</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id2</span>)
</span></span></code></pre></div><p>The part it captures is highlighted in blue in the Go code: <code>(*MyStruct, error)</code>. It makes sense, since the <code>function_declaration</code> (the part on the gray background) has a <code>result</code> child, that’s captured by <code>@id</code>.</p>
<p>Now, on to the second pattern:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span>    (<span style="color:#61afef;font-weight:bold">func_literal</span> <span style="color:#e06c75">result:</span> (<span style="color:#61afef;font-weight:bold">_</span>) <span style="color:#e06c75">@id3</span>)
</span></span></code></pre></div><p>Similarly, further down in the <code>body</code> of that function, the return type <code>(bool, error, int, string)</code> of the anonymous function is matched.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I started writing this post over a year ago. The snippet has been very stable and kept working as without changes, which is a testament to the stability of the LuaSnip (and Treesitter) APIs. I’m still using it when I write Go code, even if some corner cases (like custom structs) aren’t handled. The snippet is quite complicated and niche, so I’m not sure that I would have invested the time to write it myself. I probably don’t write enough Go code to <a href="https://xkcd.com/1205/">justify the investment</a>, but finding it shared by someone buried in their config file made it worth it.
I hope you have found this port useful, either to copy this particular snippet or as an example of advanced snippets.</p>
<p>This is the last post in a <a href="https://cj.rs/tags/luasnip/">series on LuaSnip</a>.</p>
<h2 id="resources">Resources</h2>
<p>If you want to dig deeper:</p>
<ul>
<li><a href="https://go.dev/blog/go1.13-errors">Working with Errors in Go 1.13</a></li>
<li><a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md">LuaSnip documentation</a></li>
<li>Explore the Treesitter tree for any Neovim buffer where it is active with <code>:lua vim.treesitter.inspect_tree()</code></li>
<li>Treesitter’s <a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">Pattern Matching with Queries</a></li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>The test code is available as <a href="https://gist.github.com/cljoly/0b754259df036ba0163f2cdaad48e048#file-main-go">gist</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>By the way, he has a very interesting <a href="https://www.twitch.tv/teej_dv">Twitch channel</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>At the time of writing at least. See <a href="https://perma.cc/XZW9-B2UA">perma.cc</a> and <a href="https://web.archive.org/web/20221202063059/https://github.com/tjdevries/config_manager/blob/master/README.md">the wayback machine</a> for a snapshot.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>How We Built Network Analytics V2</title><link>https://cj.rs/blog/how-we-built-network-analytics-v2/</link><pubDate>Tue, 02 May 2023 15:34:19 +0100</pubDate><guid>https://cj.rs/blog/how-we-built-network-analytics-v2/</guid><description>&lt;p&gt;I co-authored this post on the Cloudflare Blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.cloudflare.com/building-network-analytics-v2/"&gt;How we built Network Analytics v2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Archived copies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20230502165250/https://blog.cloudflare.com/building-network-analytics-v2/"&gt;WaybackMachine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://archive.is/x90WN"&gt;Archive.is&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://perma.cc/Q64U-92NL"&gt;Perma.cc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><content:encoded><![CDATA[<p>I co-authored this post on the Cloudflare Blog:</p>
<ul>
<li><a href="https://blog.cloudflare.com/building-network-analytics-v2/">How we built Network Analytics v2</a></li>
</ul>
<p>Archived copies:</p>
<ul>
<li><a href="https://web.archive.org/web/20230502165250/https://blog.cloudflare.com/building-network-analytics-v2/">WaybackMachine</a></li>
<li><a href="https://archive.is/x90WN">Archive.is</a></li>
<li><a href="https://perma.cc/Q64U-92NL">Perma.cc</a></li>
</ul>
]]></content:encoded></item><item><title>Should I Compress My Initramfs?</title><link>https://cj.rs/blog/should-i-compress-my-initramfs/</link><pubDate>Wed, 31 Aug 2022 06:47:08 +0100</pubDate><guid>https://cj.rs/blog/should-i-compress-my-initramfs/</guid><description>When decompressing is faster than reading from the disk.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>As a small start-up time optimization, you can pick the best suited compression algorithm for the initial ramdisk.</p>
  </div>



<h2 id="the-initial-ramdisk">The Initial Ramdisk</h2>
<p>When a Linux system boots, it needs to mount the root filesystem <code>/</code>.
This may be relatively complicated, as it may be on a software RAID, on LVM, encrypted…
To keep things manageable, an <a href="https://en.wikipedia.org/wiki/Initial_ramdisk">initial ramdisk</a> can be used to get a small environment that has all the required modules and configuration to load the root filesystem.
On <a href="https://archlinux.org/">Arch Linux</a>, this initial ramdisk is generated using <a href="https://wiki.archlinux.org/title/Mkinitcpio">mkinitcpio</a>.
It takes multiple parameters to tune various aspects of the system and of the generated ramdisk.</p>
<h3 id="compression">Compression</h3>
<p>One such parameter is <code>COMPRESSION</code>.
It compresses the ramdisk to make the resulting image smaller.
The <a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">manpage</a> reads:</p>




  <figure>
    <blockquote cite="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">
      <h4 id="compression-1">COMPRESSION</h4>
<p>Defines a program to filter the generated image through. The kernel understands the compression formats yielded by the zstd, gzip, bzip2, lz4, lzop, lzma, and xz compressors. If unspecified, this setting defaults to zstd compression. In order to create an uncompressed image, define this variable as cat.</p>

    </blockquote>
    
      <figcaption class="blockquote-caption">
        
          <cite style="text-align: right"><a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES</a></cite>
          <br/>
        
        
      </figcaption>
    
  </figure>



<p>Another reason to compress the image is that it may reduce the start-up time.
To understand why, imagine that the image is 100 MiB in size and only 20 MiB after compression.
Let&rsquo;s say that the disk reads 10 MiB<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> per second and that the CPU can decompress the full image in 1 second.
If we keep the image uncompressed, the disk will need <em>10 seconds</em> to read the uncompressed image, while it needs only 2 seconds to read the compressed image.
Adding the decompression time, the compressed version require only <em>3 seconds</em>.</p>
<h3 id="trade-offs">Trade-offs</h3>
<p>The above example is quite simple, but it illustrates the trade-off between a bigger image that the disk will take longer to read and a smaller image that may take longer to decompress. It is thus more of a spectrum, where more CPU-intensive compression (and decompression) methods could result in a smaller image and less read from the disk but more CPU time:</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 760 73"
      >
      <g transform='translate(8,16)'>
<path d='M 16,32 L 712,32' fill='none' stroke='currentColor'></path>
<polygon points='24.000000,32.000000 12.000000,26.400000 12.000000,37.599998' fill='currentColor' transform='rotate(180.000000, 16.000000, 32.000000)'></polygon>
<polygon points='720.000000,32.000000 708.000000,26.400000 708.000000,37.599998' fill='currentColor' transform='rotate(0.000000, 712.000000, 32.000000)'></polygon>
<text text-anchor='middle' x='16' y='4' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='16' y='20' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='16' y='52' fill='currentColor' style='font-size:1em'>u</text>
<text text-anchor='middle' x='24' y='4' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='24' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='24' y='52' fill='currentColor' style='font-size:1em'>n</text>
<text text-anchor='middle' x='32' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='32' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='32' y='52' fill='currentColor' style='font-size:1em'>c</text>
<text text-anchor='middle' x='40' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='40' y='20' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='40' y='52' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='48' y='52' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='56' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='56' y='20' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='56' y='52' fill='currentColor' style='font-size:1em'>p</text>
<text text-anchor='middle' x='64' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='64' y='20' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='64' y='52' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='72' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='72' y='20' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='72' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='80' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='80' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='88' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='88' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='96' y='52' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='104' y='52' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='368' y='52' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='376' y='52' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='384' y='52' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='640' y='4' fill='currentColor' style='font-size:1em'>l</text>
<text text-anchor='middle' x='648' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='656' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='656' y='20' fill='currentColor' style='font-size:1em'>m</text>
<text text-anchor='middle' x='664' y='4' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='664' y='20' fill='currentColor' style='font-size:1em'>o</text>
<text text-anchor='middle' x='672' y='20' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='680' y='4' fill='currentColor' style='font-size:1em'>r</text>
<text text-anchor='middle' x='680' y='20' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='688' y='4' fill='currentColor' style='font-size:1em'>e</text>
<text text-anchor='middle' x='688' y='52' fill='currentColor' style='font-size:1em'>z</text>
<text text-anchor='middle' x='696' y='4' fill='currentColor' style='font-size:1em'>a</text>
<text text-anchor='middle' x='696' y='20' fill='currentColor' style='font-size:1em'>C</text>
<text text-anchor='middle' x='696' y='52' fill='currentColor' style='font-size:1em'>s</text>
<text text-anchor='middle' x='704' y='4' fill='currentColor' style='font-size:1em'>d</text>
<text text-anchor='middle' x='704' y='20' fill='currentColor' style='font-size:1em'>P</text>
<text text-anchor='middle' x='704' y='52' fill='currentColor' style='font-size:1em'>t</text>
<text text-anchor='middle' x='712' y='4' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='712' y='20' fill='currentColor' style='font-size:1em'>U</text>
<text text-anchor='middle' x='712' y='52' fill='currentColor' style='font-size:1em'>d</text>
</g>

    </svg>
  
</div>
<p>Then, the question is: is it worth compressing an image more (or at all), to get a faster start-up time?</p>
<h2 id="protocol">Protocol</h2>
<p>To answer this question <strong>on a particular machine</strong><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>, let’s compare the time required to read and decompress various initial ramdisks.</p>
<p>I’m using the <code>linux</code> package in version <em>5.18.15-arch1-2</em> from the Arch Linux repository. Then, I generate (<code>sudo mkinitcpio -p linux</code>) various images with the following parameters in <code>/etc/mkinitcpio.conf</code>:</p>
<ul>
<li><code>COMPRESSION=&quot;cat&quot;</code></li>
<li><code>COMPRESSION=&quot;lz4&quot;</code></li>
<li><code>COMPRESSION=&quot;zstd&quot;</code></li>
</ul>
<p>Each image is copied in a directory and renamed according to the compression used: <code>cp /boot/initramfs-linux.img initramfs-linux.img.zstd</code>.
The result is as follows:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ file *.img*
</span></span><span style="display:flex;"><span>initramfs-linux.img:      ASCII cpio archive <span style="color:#56b6c2">(</span>SVR4 with no CRC<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>initramfs-linux.img.lz4:  LZ4 compressed data <span style="color:#56b6c2">(</span>v0.1-v0.9<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>initramfs-linux.img.zstd: Zstandard compressed data <span style="color:#56b6c2">(</span>v0.8+<span style="color:#56b6c2">)</span>, Dictionary ID: None
</span></span></code></pre></div><p>The images are compressing quite well too:</p>
<table id="files-size">
  <thead>
      <tr>
          <th>File</th>
          <th>Size (MiB)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>initramfs-linux.img</td>
          <td>61M</td>
      </tr>
      <tr>
          <td>initramfs-linux.img.lz4</td>
          <td>32M</td>
      </tr>
      <tr>
          <td>initramfs-linux.img.zstd</td>
          <td>22M</td>
      </tr>
  </tbody>
</table>
<p>We could compare more algorithms and compression level, but compression levels would need to be passed through <code>COMPRESSION_OPTIONS</code>, which the <a href="https://man.archlinux.org/man/mkinitcpio.conf.5.en#VARIABLES">manpage</a> discourages, as it can result in an unbootable image.</p>
<h2 id="results">Results</h2>
<p>Let’s run some decompression commands and compare their run-time with <a href="https://github.com/sharkdp/hyperfine">hyperfine</a>. On a quiet computer:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sh" data-lang="sh"><span style="display:flex;"><span>$ hyperfine <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    --prepare <span style="color:#98c379">&#39;sync; echo 3 | sudo tee /proc/sys/vm/drop_caches&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;lz4 -d &lt;./initramfs-linux.img.lz4&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;zstd -d &lt;initramfs-linux.img.zstd&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;cat &lt;initramfs-linux.img&#39;</span>
</span></span></code></pre></div><p>Note that the command has a <code>--prepare 'sync; echo 3 | sudo tee /proc/sys/vm/drop_caches'</code> argument.
This empties the OS file system caches to be closer to start-up conditions: when the computer starts, everything has to be read from the disk as the RAM is basically empty.
Without this <code>--prepare</code> argument, we get much shorter times, e.g. 45ms for lz4.</p>
<p>Here are the results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>lz4 -d &lt;./initramfs-linux.img.lz4</code></td>
          <td style="text-align: right">137.9 ± 13.5</td>
          <td style="text-align: right">122.8</td>
          <td style="text-align: right">157.4</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>zstd -d &lt;initramfs-linux.img.zstd</code></td>
          <td style="text-align: right">164.9 ± 13.4</td>
          <td style="text-align: right">153.6</td>
          <td style="text-align: right">187.9</td>
          <td style="text-align: right">1.20 ± 0.15</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>cat &lt;initramfs-linux.img</code></td>
          <td style="text-align: right">175.9 ± 19.0</td>
          <td style="text-align: right">157.9</td>
          <td style="text-align: right">218.8</td>
          <td style="text-align: right">1.28 ± 0.19</td>
      </tr>
  </tbody>
</table>
<p>Lz4 is slightly faster, followed by zstd and no compression at all with <code>cat</code>.
If we go back to the <a href="#files-size">sizes</a> table, the trade-off between a smaller image but a slower decompression is clear.
Despite a ~30% smaller file size, zstd is still a bit slower to decompress than lz4, while no compression at all is even worse.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The above results are based on runs on a particular machine.
As mentioned different machines will yield different results, depending on the relative performance of the disk and the CPU.
It’s also a pretty small improvement in the grand scheme of things: only a few tens of milliseconds on a process that takes a couple seconds.
But I found it to be a nice example of how compression can make things faster, compared to no compression at all, because CPU nowadays are so fast.</p>
<hr>
<h2 id="appendix-recording-of-the-hyperfine-run">Appendix: Recording of the <code>hyperfine</code> Run</h2>
<p>This was done on a different run from the table above, as running the benchmark through Asciinema is sometimes a bit less stable):
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/should-i-compress-my-initramfs/hyperfine.json", document.getElementById('demo3'), {
"autoPlay":  false ,"loop":  false ,"poster": "npt:0:16",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/blog/should-i-compress-my-initramfs/hyperfine.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>
</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <ul>
<li>2022-08-02: Slightly reworded the intro to account for <a href="https://fosstodon.org/@pixelherodev/108927525223368261">https://fosstodon.org/@pixelherodev/108927525223368261</a></li>
<li>2022-11-13: As pointed out in <a href="https://www.reddit.com/r/archlinux/comments/ytk6t3/comment/iw74omr/">this comment on Reddit</a>, sometimes one wants to trade CPU time for disc space:




  <figure>
    <blockquote >
      <p>Another point: by default, Windows made a 100Mo EFI partition on my system. To conserve the dual boot capabilities without having to modify my partition scheme in ways I am not comfortable with, I have to compress my initramfs or it will literally not fit in the partition.</p>

    </blockquote>
    
  </figure>



</li>
<li>2022-11-14: Clarify that the 10 MiB reading speed is made up for the example.</li>
</ul>
  </div>



<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That’s a completely made up number for the sake of the example.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>The conclusions will in all likelihood change depending on the machine, namely the relative performance of the CPU and the disk.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Generating Snippets with LuaSnip in Neovim</title><link>https://cj.rs/blog/generating-snippets-with-luasnip/</link><pubDate>Sun, 31 Jul 2022 22:10:49 +0100</pubDate><guid>https://cj.rs/blog/generating-snippets-with-luasnip/</guid><description>Generating repetitive snippets with code, like time variant of section titles.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>When you have many variations of the same snippet, one option is to generate those with Lua code. The complete example is <a href="#the-final-code">at the end</a>.</p>
  </div>



<p>I’ve <a href="https://cj.rs/blog/ultisnips-to-luasnip/">recently moved</a> to <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> as my snippets plugin for Neovim.
When I first started, I sticked to the simplest features of LuaSnip, in particular the SnipMate-like syntax for snippets.
But I have now started to explore the more distinctive features of LuaSnip, like Lua-defined snippets.
It turns out that generating snippets with code can save tedious repetition.</p>
<h2 id="markdown-journaling">Markdown Journaling</h2>
<p>I tend to take notes in Markdown documents.
I usually set up one text file for each recurring themes or project.
Then, there is a “h1” title per day, a bit like the <a href="https://help.bulletjournal.com/article/27-daily-log">Bullet Journal daily log</a>.
The resulting file looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex; background-color:#3d4148"><span># 2022-07-31 Sun
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 1
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 2
</span></span><span style="display:flex;"><span><span style="color:#c678dd">*</span> Point 3
</span></span></code></pre></div><p>I have a snippet to generate the <code># 2022-07-31 Sun</code> line, based on the current date and day of the week, as I explained <a href="https://cj.rs/blog/ultisnips-to-luasnip/#advanced-snippets-with-environment-variables">last time</a>.
For reference, here is the snippet from back then:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet"># “Bullet Journal”-styled date for today
snippet bjtoday
  # ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} $CURRENT_DAY_NAME_SHORT
</code></pre><h2 id="lua-generated-snippets">Lua-Generated Snippets</h2>
<p>But then, I also sometimes want to put notes for the next session.
I usually know when that will be, because the meeting happens, say, every Thursday or every month.
So it would be great to have snippets for those dates as well.
Let’s build them step by step.</p>
<h3 id="1-computing-the-date-of-next-friday">1. Computing the Date of “Next Friday”</h3>
<p>The first step is to calculate the date and weekday from human concepts like “next Friday” or “next week”.
Thankfully <a href="https://www.gnu.org/software/coreutils/manual/html_node/date-invocation.html#date-invocation">GNU date</a> supports this, and it’s preinstalled on most GNU/Linux systems:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ date -d <span style="color:#98c379">&#39;next Friday&#39;</span> +<span style="color:#98c379">&#39;%F %a&#39;</span>
</span></span><span style="display:flex;"><span>2022-08-05 Fri
</span></span></code></pre></div><h3 id="2-putting-those-dates-in-luasnip">2. Putting Those Dates in LuaSnip</h3>
<p>The second step is to use the output of the <code>date</code> command in LuaSnip.</p>
<p>We could <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#variables">define our own variables</a> like <code>${NEXT_MONDAY_DATE}</code>.
That would be similar to the <code>${CURRENT_DATE}</code> we used before.
But we would need many such variables, for the date and weekday of the next Monday, Tuesday, …, next week or next month.
Plus I use those dates only in Markdown snippets, so it feels a bit wasteful to define those variables everywhere.</p>
<p>In the end, I chose to use <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#functionnode">function nodes</a> to call the <code>date</code> command on the fly, when the snippet gets expanded.
This node is inserted in a particular snippet definition and is thus contained to that particular snippet.
This way, we avoid creating values everywhere, and we keep the global namespace clean.
It looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>, <span style="color:#e06c75">snip</span>, <span style="color:#e06c75">user_arg_1</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#c678dd">return</span> <span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">trim</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">system</span>(<span style="color:#98c379">[[date -d &#39;]]</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span> <span style="color:#56b6c2">..</span> <span style="color:#98c379">[[&#39; +&#39;%F %a&#39;]]</span>))
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>, {}),
</span></span></code></pre></div><h3 id="3-generating-snippets-variants">3. Generating Snippets Variants</h3>
<p>The last step is to generate the variants of the base snippet: <code>bj_today</code>, <code>bj_next_monday</code>, …, <code>bj_next_week</code> and <code>bj_next_month</code>.</p>
<p>In LuaSnip, snippets defined using Lua are just a table calling some agreed-upon functions.
Here is an example from the <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#basics">documentation</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">ls.add_snippets</span>(<span style="color:#98c379">&#34;all&#34;</span>, {
</span></span><span style="display:flex;"><span>	<span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;ternary&#34;</span>, {
</span></span><span style="display:flex;"><span>		<span style="color:#7f848e">-- equivalent to &#34;${1:cond} ? ${2:then} : ${3:else}&#34;</span>
</span></span><span style="display:flex;"><span>		<span style="color:#e06c75">i</span>(<span style="color:#d19a66">1</span>, <span style="color:#98c379">&#34;cond&#34;</span>), <span style="color:#e06c75">t</span>(<span style="color:#98c379">&#34; ? &#34;</span>), <span style="color:#e06c75">i</span>(<span style="color:#d19a66">2</span>, <span style="color:#98c379">&#34;then&#34;</span>), <span style="color:#e06c75">t</span>(<span style="color:#98c379">&#34; : &#34;</span>), <span style="color:#e06c75">i</span>(<span style="color:#d19a66">3</span>, <span style="color:#98c379">&#34;else&#34;</span>)
</span></span><span style="display:flex;"><span>	})
</span></span><span style="display:flex;"><span>})
</span></span></code></pre></div><p>This creates a snippet (the <code>s</code> function) for <code>all</code> file types. This snippet will expand <code>ternary</code> into <code>cond ? then : else</code>, prompting the user to change the <code>cond</code>, <code>then</code> and <code>else</code> bits (function <code>i</code>) while <code>?</code> and <code>:</code> are “static text” (function <code>t</code>).</p>
<p>We want to write a function that builds a table of date snippet definitions.
That will be returned when the Lua module is loaded.</p>
<h4 id="the-final-code">The Final Code</h4>
<p>The final snippet looks like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span><span style="color:#7f848e">-- Bullet Journal styled dates in the future</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span><span style="color:#c678dd">local</span> <span style="color:#c678dd">function</span> <span style="color:#61afef;font-weight:bold">gen_date_snippets</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">snippets</span> <span style="color:#56b6c2">=</span> {}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span>  <span style="color:#c678dd">local</span> <span style="color:#e06c75">target_dates</span> <span style="color:#56b6c2">=</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>    <span style="color:#98c379">&#34;today&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span>    <span style="color:#98c379">&#34;tomorrow&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span>    <span style="color:#98c379">&#34;next monday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>    <span style="color:#98c379">&#34;next tuesday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>    <span style="color:#98c379">&#34;next wednesday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span>    <span style="color:#98c379">&#34;next thursday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span>    <span style="color:#98c379">&#34;next friday&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span>    <span style="color:#98c379">&#34;next week&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span>    <span style="color:#98c379">&#34;next month&#34;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">14</span><span>  }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">15</span><span>  <span style="color:#c678dd">for</span> <span style="color:#e06c75">_</span>, <span style="color:#e06c75">target_date</span> <span style="color:#c678dd">in</span> <span style="color:#e06c75">pairs</span>(<span style="color:#e06c75">target_dates</span>) <span style="color:#c678dd">do</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">16</span><span>    <span style="color:#e06c75">table.insert</span>(
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">17</span><span>      <span style="color:#e06c75">snippets</span>,
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">18</span><span>      <span style="color:#e06c75">s</span>(<span style="color:#98c379">&#34;bj_&#34;</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span>:<span style="color:#e06c75">gsub</span>(<span style="color:#98c379">&#34; &#34;</span>, <span style="color:#98c379">&#34;_&#34;</span>), {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">19</span><span>        <span style="color:#e06c75">t</span> <span style="color:#98c379">&#34;# &#34;</span>,
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">20</span><span>        <span style="color:#e06c75">f</span>(<span style="color:#c678dd">function</span>(<span style="color:#e06c75">args</span>, <span style="color:#e06c75">snip</span>, <span style="color:#e06c75">user_arg_1</span>)
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">21</span><span>          <span style="color:#c678dd">return</span> <span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">trim</span>(<span style="color:#e06c75">vim.fn</span>.<span style="color:#e06c75">system</span>(<span style="color:#98c379">[[date -d &#39;]]</span> <span style="color:#56b6c2">..</span> <span style="color:#e06c75">target_date</span> <span style="color:#56b6c2">..</span> <span style="color:#98c379">[[&#39; +&#39;%F %a&#39;]]</span>))
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">22</span><span>        <span style="color:#c678dd">end</span>, {}),
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">23</span><span>      })
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">24</span><span>    )
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">25</span><span>  <span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">26</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">27</span><span>  <span style="color:#c678dd">return</span> <span style="color:#e06c75">snippets</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">28</span><span><span style="color:#c678dd">end</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">29</span><span>
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">30</span><span><span style="color:#c678dd">return</span> <span style="color:#e06c75">gen_date_snippets</span>()
</span></span></code></pre></div><p>This generates the snippets name (line 18), for instance <code>bj_today</code> or <code>bj_next_tuesday</code> from the arguments passed to the <code>date</code> command.
The body of the snippet is made of text (<code>t &quot;# &quot;</code>) and our function node from earlier (line 20-22).
All those snippets get collected in a table that is returned at the end of the Lua module (line 30).
It’s then usable by LuaSnip.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Generating a bunch of similar snippets like this turned out to be an unanticipated benefit of using Lua to define snippets.
Before using LuaSnip, I only had <code>bj_today</code>, <code>bj_tomorrow</code> and <code>bj_next_monday</code>.
If I had wanted to expand this set further, I would have to do some more copying and pasting, which quickly becomes unmaintainable.</p>
<p>Of course for simpler or less repetitive snippets, it’s probably best to use the less verbose SnipMate-like syntax presented in the <a href="https://cj.rs/blog/ultisnips-to-luasnip/">previous post</a>.</p>
]]></content:encoded></item><item><title>Rust Default Values for Maintainability</title><link>https://cj.rs/blog/rust-default-values-for-maintainability/</link><pubDate>Sat, 25 Jun 2022 14:41:55 +0000</pubDate><guid>https://cj.rs/blog/rust-default-values-for-maintainability/</guid><description>Hidden benefits of the humble Default trait</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>The <a href="https://doc.rust-lang.org/std/default/trait.Default.html"><code>Default</code></a> trait can enhance the maintainability of your code. Default values for common types are <a href="#quick-reference">listed</a> at the end.</p>
  </div>



<h2 id="a-pr-review">A PR Review</h2>
<p>Recently, while reviewing a <a href="https://github.com/cljoly/rusqlite_migration/pull/20/files">PR</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, I noticed that part of the patch was introducing a new field to a struct:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span>diff --git a/src/lib.rs b/src/lib.rs
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span>index eba9a3a..8619e06 100644
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span><span style="color:#e06c75">--- a/src/lib.rs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span><span style="color:#98c379;font-weight:bold">+++ b/src/lib.rs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>@@ -106,8 +108,9 @@ use std::{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span> #[derive(Debug, PartialEq, Clone)]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span> pub struct M&lt;&#39;u&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>     up: &amp;&#39;u str,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>     down: Option&lt;&amp;&#39;u str&gt;,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span><span style="color:#98c379;font-weight:bold">+    foreign_key_check: bool,
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span> }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span> impl&lt;&#39;u&gt; M&lt;&#39;u&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">14</span><span>@@ -137,8 +140,9 @@ impl&lt;&#39;u&gt; M&lt;&#39;u&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">15</span><span>     pub const fn up(sql: &amp;&#39;u str) -&gt; Self {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">16</span><span>         Self {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">17</span><span>             up: sql,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">18</span><span>             down: None,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">19</span><span><span style="color:#98c379;font-weight:bold">+            foreign_key_check: false,
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">20</span><span>         }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">21</span><span>     }
</span></span></code></pre></div><p>That prompted me to reflect on the code I had initially written.
Prior to the patch, it looked roughly<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span><span style="color:#7f848e">#[derive(Debug, PartialEq, Clone)]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span><span style="color:#c678dd">pub</span> <span style="color:#c678dd">struct</span> <span style="color:#e5c07b">M</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span>    <span style="color:#e06c75">up</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span>    <span style="color:#e06c75">down</span>: <span style="color:#e5c07b">Option</span><span style="color:#56b6c2">&lt;&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span><span style="color:#56b6c2">&gt;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span><span style="color:#c678dd">impl</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> <span style="color:#e06c75">M</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>    <span style="color:#c678dd">pub</span> <span style="color:#c678dd">const</span> <span style="color:#c678dd">fn</span> <span style="color:#61afef;font-weight:bold">up</span>(<span style="color:#e06c75">sql</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span>) -&gt; <span style="color:#e5c07b">Self</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>        <span style="color:#e5c07b">Self</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span>            <span style="color:#e06c75">up</span>: <span style="color:#e5c07b">sql</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span>            <span style="color:#e06c75">down</span>: <span style="color:#e5c07b">None</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">14</span><span>}
</span></span></code></pre></div><h2 id="the-default-trait">The <code>Default</code> Trait</h2>
<p>What if I had used the <a href="https://doc.rust-lang.org/std/default/trait.Default.html"><code>Default</code></a> trait here?
The code could have looked like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-rust" data-lang="rust"><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span><span style="color:#7f848e">#[derive(Debug, Default, PartialEq, Clone)]</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span><span style="color:#c678dd">pub</span> <span style="color:#c678dd">struct</span> <span style="color:#e5c07b">M</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span>    <span style="color:#e06c75">up</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span>    <span style="color:#e06c75">down</span>: <span style="color:#e5c07b">Option</span><span style="color:#56b6c2">&lt;&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span><span style="color:#56b6c2">&gt;</span>,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span><span style="color:#c678dd">impl</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> <span style="color:#e06c75">M</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;u</span><span style="color:#56b6c2">&gt;</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>    <span style="color:#c678dd">pub</span> <span style="color:#c678dd">const</span> <span style="color:#c678dd">fn</span> <span style="color:#61afef;font-weight:bold">up</span>(<span style="color:#e06c75">sql</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;u</span> <span style="color:#e5c07b">str</span>) -&gt; <span style="color:#e5c07b">Self</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>        <span style="color:#e5c07b">Self</span> {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span>            <span style="color:#e06c75">up</span>: <span style="color:#e5c07b">sql</span>,
</span></span><span style="display:flex; background-color:#3d4148"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span>            <span style="color:#56b6c2">..</span><span style="color:#e5c07b">Default</span>::<span style="color:#e06c75">default</span>()
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span>        }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span>    }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">14</span><span>}
</span></span></code></pre></div><p>On the first line, the <a href="https://doc.rust-lang.org/reference/attributes/derive.html"><code>#[derive(Default)]</code></a> attribute makes the structure <code>M</code> implement the <code>Default</code> trait.
Thanks to this trait, a call to <code>M::default()</code> will create a struct with default values for its fields: <code>M { up: &quot;&quot;, down: None }</code>. Note that when a structure <code>M</code> is expected, <code>Default::default()</code> is equivalent to <code>M::default()</code>.</p>
<p>We then need to initialize the two fields of that structure, overriding some defaults:</p>
<ul>
<li><code>up</code> is defined directly as before. That’s the value we want to override.</li>
<li><code>down</code> is set by the <code>Default</code> trait. This is done by <code>..Default::default()</code> on line 11. <code>Default::default()</code> provides the values. Then the <a href="https://doc.rust-lang.org/reference/expressions/struct-expr.html#functional-update-syntax"><code>..</code></a> syntax fills out the fields that were not directly set. <code>down</code> is thus set to the same value as before, <code>None</code>.</li>
</ul>
<p>The code is just as long as before, when we were not using the <code>Default</code> trait.
But then, <a href="#a-pr-review">line 19 of the above patch</a> would have been unnecessary: <code>false</code> is the default for a <code>bool</code>, so the new <code>foreign_key_check</code> field would have been covered by the <code>..Default::default()</code>.</p>
<p>This results in a shorter patch:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 1</span><span>diff --git a/src/lib.rs b/src/lib.rs
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 2</span><span>index eba9a3a..8619e06 100644
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 3</span><span><span style="color:#e06c75">--- a/src/lib.rs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 4</span><span><span style="color:#98c379;font-weight:bold">+++ b/src/lib.rs
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 5</span><span>@@ -106,8 +108,9 @@ use std::{
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 6</span><span> #[derive(Debug, PartialEq, Clone)]
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 7</span><span> pub struct M&lt;&#39;u&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 8</span><span>     up: &amp;&#39;u str,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f"> 9</span><span>     down: Option&lt;&amp;&#39;u str&gt;,
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">10</span><span><span style="color:#98c379;font-weight:bold">+    foreign_key_check: bool,
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">11</span><span> }
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">12</span><span> 
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#55595f">13</span><span> impl&lt;&#39;u&gt; M&lt;&#39;u&gt; {
</span></span></code></pre></div><h2 id="conclusion">Conclusion</h2>
<p>In the example of this post, we are doing only one instantiation of that particular struct, and it has very few fields anyway.
But if there were many instantiations of that struct, we would have had to change all of those.
Then, using <code>Default</code> would have been quite beneficial.</p>
<p><a href="https://cs.github.com/rust-lang/rust/blob/10f4ce324baf7cfb7ce2b2096662b82b79204944/compiler/rustc_target/src/spec/hermit_base.rs#L21">This</a>
<a href="https://cs.github.com/rust-lang/rust/blob/10f4ce324baf7cfb7ce2b2096662b82b79204944/compiler/rustc_target/src/spec/solaris_base.rs#L15">pattern</a>
<a href="https://cs.github.com/rust-lang/rust-analyzer/blob/6fc5c3cd2117a29981ba9b7cef8a51c1d6804089/crates/ide-completion/src/render.rs#L68">were</a>
<a href="https://cs.github.com/rust-lang/rustc-perf/blob/434ba59ca9fbd793ba2b6d02e65704c108479069/collector/benchmarks/clap-3.1.6/src/build/possible_value.rs#L56">the</a>
<a href="https://cs.github.com/rust-lang/rustup/blob/20ed5d9803ca237c39fbbcba8971c4558be4acca/src/diskio/immediate.rs#L34">return</a>
<a href="https://cs.github.com/rust-lang/mdBook/blob/0547868d4d25e1c840a871f9e17b2b4c2078596b/src/book/book.rs#L89">type</a>
is
<a href="https://cs.github.com/rust-lang/rust/blob/10f4ce324baf7cfb7ce2b2096662b82b79204944/compiler/rustc_target/src/spec/redox_base.rs#L16">built</a>
<a href="https://cs.github.com/rust-lang/docs.rs/blob/1ce3fd876a0f5fd5b52c9962cf7ae9df137e6366/src/web/releases.rs#L653">with</a>
a
<a href="https://cs.github.com/rust-lang/rust/blob/10f4ce324baf7cfb7ce2b2096662b82b79204944/compiler/rustc_target/src/spec/l4re_base.rs#L13">call</a>
to
<code>Default::default()</code>
seems
relatively
common
in
the
<a href="https://github.com/rust-lang">Rust organization</a>.
It can enhance maintainability, much more than I initially thought.</p>
<p>Of course this is a balancing act.
For instance, this pattern could be abused by <a href="https://play.rust-lang.org/?version=stable&amp;mode=debug&amp;edition=2021&amp;gist=ae7553d16b481951bd8e3418b82fcd27">defining custom default values</a> on primitive types for a particular structure.
That would lead to <code>Default::default()</code> filling surprising values and the code would be less predictable.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-07-29: As SpudnikV <a href="https://www.reddit.com/r/rust/comments/vkozed/comment/idtbgit/?utm_source=share&amp;utm_medium=web2x&amp;context=3">pointed out</a>, using defaults as explained in this post can hide the implications of a change made to a structure. There could be code in various places relying on invariants that may break due to the change. Without <code>Default::default()</code>, the change might make visible edits in these places, drawing the attention of reviewers on these invariants.</p>
<p>That’s another case where it might not be wise to use the <code>Default</code> trait. Again, it’s a balancing act!</p>
  </div>



<hr>
<h2 id="appendix-defaults-for-some-common-types-with-derive">Appendix: Defaults for Some Common Types With <code>derive</code></h2>
<p>Why did I not use the <code>Default</code> trait initially?
Part of it might be the fear of introducing incorrect code.
It was slightly unclear to me what <code>derive</code> uses as a default value for common primitive types.
And you don’t want a field set explicitly to <code>false</code> becoming a <code>true</code> once you use <code>Default</code>, right?</p>
<p>Let’s take a closer look by running the following program:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#7f848e">#[derive(Debug, Default)]</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">struct</span> <span style="color:#e5c07b">D</span><span style="color:#56b6c2">&lt;</span><span style="color:#e06c75">&#39;a</span><span style="color:#56b6c2">&gt;</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">b</span>: <span style="color:#e5c07b">bool</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">c</span>: <span style="color:#e5c07b">char</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">o</span>: <span style="color:#e5c07b">Option</span><span style="color:#56b6c2">&lt;</span><span style="color:#e5c07b">usize</span><span style="color:#56b6c2">&gt;</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">string</span>: <span style="color:#e5c07b">String</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e5c07b">str</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;a</span> <span style="color:#e5c07b">str</span>,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">v</span>: <span style="color:#e5c07b">Vec</span><span style="color:#56b6c2">&lt;</span><span style="color:#e5c07b">usize</span><span style="color:#56b6c2">&gt;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">a</span>: [<span style="color:#e5c07b">usize</span>; <span style="color:#d19a66">10</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">s</span>: <span style="color:#c678dd">&amp;</span><span style="color:#e06c75">&#39;a</span> [<span style="color:#e5c07b">u32</span>],
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">f</span>: <span style="color:#e5c07b">f64</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">u</span>: (<span style="color:#e5c07b">usize</span>, <span style="color:#e5c07b">u32</span>, <span style="color:#e5c07b">u64</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">fn</span> <span style="color:#61afef;font-weight:bold">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#56b6c2;font-weight:bold">println!</span>(<span style="color:#98c379">&#34;</span><span style="color:#98c379">{:#?}</span><span style="color:#98c379">&#34;</span>, <span style="color:#e06c75">D</span>::<span style="color:#e06c75">default</span>());
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Output:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-rust" data-lang="rust"><span style="display:flex;"><span><span style="color:#e06c75">D</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">b</span>: <span style="color:#e5c07b">false</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">c</span>: <span style="color:#e06c75">&#39;</span>\<span style="color:#d19a66">0</span><span style="color:#e06c75">&#39;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">o</span>: <span style="color:#e5c07b">None</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">string</span>: <span style="color:#98c379">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e5c07b">str</span>: <span style="color:#98c379">&#34;&#34;</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">v</span>: [],
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">a</span>: [
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">s</span>: [],
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">f</span>: <span style="color:#d19a66">0.0</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#e06c75">u</span>: (
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#d19a66">0</span>,
</span></span><span style="display:flex;"><span>    ),
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>It turns out that in general, the default for common types in the standard library is a 0 byte in the underlying data structure.
Note that arrays are fixed size in rust and thus the default is an array of the right size, filled with defaults for the inner type.
That’s quite similar to <a href="https://go.dev/ref/spec#The_zero_value">go</a>.</p>
<h3 id="quick-reference">Quick Reference</h3>
<p>Here is a table for future reference:</p>
<table>
  <thead>
      <tr>
          <th>Type</th>
          <th>Default value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>bool</code></td>
          <td><code>false</code></td>
      </tr>
      <tr>
          <td><code>char</code></td>
          <td><code>'\0'</code></td>
      </tr>
      <tr>
          <td><code>Option</code></td>
          <td><code>None</code></td>
      </tr>
      <tr>
          <td><code>String</code>, <code>&amp;str</code></td>
          <td><code>&quot;&quot;</code></td>
      </tr>
      <tr>
          <td><code>Vec&lt;usize&gt;</code></td>
          <td><code>[]</code></td>
      </tr>
      <tr>
          <td><code>[usize; N]</code></td>
          <td><code>[0, 0, …, 0]</code></td>
      </tr>
      <tr>
          <td><code>&amp;[u32]</code></td>
          <td><code>[]</code></td>
      </tr>
      <tr>
          <td><code>f64</code>, <code>f32…</code></td>
          <td><code>0.</code></td>
      </tr>
      <tr>
          <td><code>usize</code>, <code>u32…</code></td>
          <td><code>0</code></td>
      </tr>
  </tbody>
</table>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Please don’t take anything in this post as critical of the PR’s author work. I’m very grateful that they took some time to contribute to <a href="https://cj.rs/rusqlite_migration/">the project</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>I’ve slightly edited the patch and the code samples from <a href="https://cj.rs/rusqlite_migration/">rusqlite_migration</a> to make those shorter and easier to grasp.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>From UltiSnips to LuaSnip</title><link>https://cj.rs/blog/ultisnips-to-luasnip/</link><pubDate>Sun, 15 May 2022 07:06:49 +0100</pubDate><guid>https://cj.rs/blog/ultisnips-to-luasnip/</guid><description>Shaving off a few milliseconds from Neovim startup time.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> is fast and doesn’t have to be complicated. Give it a try!</p>
  </div>







  
  
  
    
    
  
  

  <details class="alert alert-note">
    <summary class="alert-heading">
      ℹ️
      
        About UltiSnips
      
    </summary>
    <p>Even if that article shows how <a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> shines, I have great respect for the work that has gone into <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. It is still a reliable, reasonably fast plugin given the constraint it operates in (in particular, Vim compatibility requires a fair amount of Vimscript). I’ve written this article shortly after trying LuaSnip and I’m still very much evaluating it.</p>
  </details>



<h2 id="introduction">Introduction</h2>
<p><a href="https://en.wikipedia.org/wiki/Snippet_(programming)">Snippets</a> are a convenient feature of some text editors to insert and adapt reusable pieces of code. For instance, snippets for <code>for</code> loops are common, to get the tedious bits of the syntax out of the way.</p>
<p>To get this feature in Vim back in the days, I started using <a href="https://github.com/sirver/UltiSnips">UltiSnips</a>. There are <a href="https://github.com/honza/vim-snippets">default snippets sets</a> for it, and it’s easy to write custom snippets. These custom snippet can call Bash or Python scripts if you need more dynamic replacements. UltiSnips has been very powerful and has served me quite well over the past decade or so, and I have kept it when I migrated to NeoVim a few years ago.</p>
<h2 id="startup-time">Startup time</h2>
<p>Every once in a while though, I run the excellent <a href="https://github.com/rhysd/vim-startuptime">vim-startuptime</a> command to assess the impact of various configurations and plugins on the startup time of Neovim. With UltiSnips and the corresponding completion plugins in my configuration, the first few lines are:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 104.218300 msec
</span></span><span style="display:flex;"><span>Total Max:     109.719000 msec
</span></span><span style="display:flex;"><span>Total Min:     99.533000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>64.218600 70.419000 61.066000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 6.255900  7.238000  5.764000: loading packages
</span></span><span style="display:flex;"><span> 3.939000  5.338000  3.533000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 3.651100  4.003000  3.381000: loading rtp plugins
</span></span><span style="display:flex;"><span> 2.761600  3.957000  2.474000: expanding arguments
</span></span><span style="display:flex;"><span> 2.683900  3.035000  2.566000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.418700  3.610000  2.131000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 2.389600  2.751000  2.228000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 1.954900  2.293000  1.702000: loading after plugins
</span></span><span style="display:flex;"><span> 1.842500  2.015000  1.763000: BufEnter autocommands
</span></span><span style="display:flex; background-color:#3d4148"><span> 1.583350  3.532000  0.018000: ~/.local/share/nvim/site/pack/paqs/start/ultisnips/plugin/UltiSnips.vim
</span></span><span style="display:flex;"><span> 1.027700  1.170000  0.831000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex; background-color:#3d4148"><span> 0.968200  1.071000  0.860000: ~/.local/share/nvim/site/pack/paqs/start/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua
</span></span><span style="display:flex;"><span> 0.859000  1.014000  0.702000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.557200  0.817000  0.493000: init highlight
</span></span><span style="display:flex;"><span> 0.553200  0.898000  0.322000: opening buffers</span></span></code></pre></div>
<p>The <code>init.lua</code> line covers a lot of different plugins and mappings set up in that file. Besides the <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> colorscheme<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, the next biggest contributor is <code>~/…/ultisnips/plugin/UltiSnips.vim</code>. The script to integrate UltiSnips with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a>, <code>~/…/cmp-nvim-ultisnips/after/plugin/cmp_nvim_ultisnips.lua</code>, is not far behind. In total, we spend nearly 4 ms of the startup time for UltiSnips-related files, on top of the setup done in <code>init.lua</code>. That feels suboptimal, so I’ve been looking for a possible snippet plugin alternative.</p>
<h2 id="installing-luasnip">Installing LuaSnip</h2>
<p><a href="https://github.com/L3MON4D3/LuaSnip">LuaSnip</a> aims to be a faster<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> snippet engine, with support of treesitter in the snippets. It can also understand the LSP snippet “format”. Finally, it’s a pure Lua plugin, without any Python requirement, contrary to UltiSnips. That makes installation on various system much easier.</p>
<p>Its drawbacks for me are that it does not support the same snippet definition as UltiSnips and even in its own SnipMate-like syntax, backticks to execute code are not supported. As a result, I’ll have to migrate my (admittedly very small) snippet collection. Conversely, if I want to do more advanced things, I’ll have to learn the relatively complex “VS Studio Code” snippets in JSON or even the pure Lua snippets.</p>
<p>That’s said, I believe I can get the LuaSnip benefits without writing Lua snippets or JSON ones, at least to start. So let’s try and do that, using only the SnipMate-like syntax!</p>
<h2 id="overview-of-the-configuration-changes">Overview of the Configuration Changes</h2>
<p>I’ve made the following changes.</p>
<ol>
<li>Remove UltiSnips and its associated plugins, namely for completion with <a href="https://github.com/hrsh7th/nvim-cmp">cmp</a> and the telescope integration.</li>
<li>Remove the UltiSnips mappings.</li>
<li>Add LuaSnip (following the instructions in the <a href="https://github.com/L3MON4D3/LuaSnip">Readme</a>), <a href="https://github.com/saadparwaiz1/cmp_luasnip">its cmp integration</a>, a <a href="https://github.com/rafamadriz/friendly-snippets">set of snippets</a> and a <a href="https://github.com/benfowler/telescope-luasnip.nvim">telescope integration</a> along with some mappings (requires nvim 0.7+<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>):
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">set</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;C-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip forward jump&#34;</span> })
</span></span><span style="display:flex;"><span><span style="color:#e06c75">vim.keymap</span>.<span style="color:#e06c75">se</span>({ <span style="color:#98c379">&#34;i&#34;</span>, <span style="color:#98c379">&#34;s&#34;</span> }, <span style="color:#98c379">&#34;&lt;M-i&gt;&#34;</span>, <span style="color:#c678dd">function</span>() <span style="color:#e06c75">require</span><span style="color:#98c379">&#39;luasnip&#39;</span>.<span style="color:#e06c75">jump</span>(<span style="color:#56b6c2">-</span><span style="color:#d19a66">1</span>) <span style="color:#c678dd">end</span>, { <span style="color:#e06c75">desc</span> <span style="color:#56b6c2">=</span> <span style="color:#98c379">&#34;LuaSnip backward jump&#34;</span> })
</span></span></code></pre></div></li>
<li><a href="#migrate-my-snippet-collection">Migrate my existing snippets</a>, see below.</li>
<li>Add code to load the snippet set and my own snippet collection:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-lua" data-lang="lua"><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_vscode&#34;</span>).<span style="color:#e06c75">lazy_load</span>()
</span></span><span style="display:flex;"><span><span style="color:#e06c75">require</span>(<span style="color:#98c379">&#34;luasnip.loaders.from_snipmate&#34;</span>).<span style="color:#e06c75">lazy_load</span>({ <span style="color:#e06c75">paths</span> <span style="color:#56b6c2">=</span> {<span style="color:#98c379">&#34;./snippets&#34;</span>} })
</span></span></code></pre></div></li>
</ol>
<h3 id="after">After</h3>
<p>Let’s run vim-startuptime again. Here are the new top contributors:
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;display:grid;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span>Extra options: []
</span></span><span style="display:flex;"><span>Measured: 10 times
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Total Average: 98.251400 msec
</span></span><span style="display:flex;"><span>Total Max:     100.159000 msec
</span></span><span style="display:flex;"><span>Total Min:     96.519000 msec
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  AVERAGE       MAX       MIN
</span></span><span style="display:flex;"><span>------------------------------
</span></span><span style="display:flex; background-color:#3d4148"><span>60.469100 62.341000 59.619000: ~/.config/nvim/init.lua
</span></span><span style="display:flex;"><span> 5.991100  6.219000  5.865000: loading packages
</span></span><span style="display:flex;"><span> 4.088400  4.162000  3.942000: loading after plugins
</span></span><span style="display:flex;"><span> 3.635700  3.752000  3.415000: loading rtp plugins
</span></span><span style="display:flex;"><span> 3.561800  3.936000  3.220000: ~/.local/share/nvim/site/pack/paqs/start/onedark.nvim/colors/onedark.lua
</span></span><span style="display:flex;"><span> 2.550500  2.628000  2.492000: reading ShaDa
</span></span><span style="display:flex;"><span> 2.454200  2.525000  2.401000: expanding arguments
</span></span><span style="display:flex;"><span> 2.363000  2.420000  2.270000: /usr/share/nvim/runtime/filetype.lua
</span></span><span style="display:flex;"><span> 2.271100  2.337000  2.146000: sourcing vimrc file(s)
</span></span><span style="display:flex;"><span> 1.701000  1.767000  1.635000: BufEnter autocommands
</span></span><span style="display:flex;"><span> 1.676700  2.049000  1.366000: opening buffers
</span></span><span style="display:flex;"><span> 1.034100  1.280000  0.782000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter/plugin/nvim-treesitter.lua
</span></span><span style="display:flex;"><span> 0.901000  1.021000  0.799000: ~/.local/share/nvim/site/pack/paqs/start/nvim-treesitter-textobjects/plugin/nvim-treesitter-textobjects.vim
</span></span><span style="display:flex;"><span> 0.590700  0.674000  0.524000: ~/.local/share/nvim/site/pack/paqs/start/indent-blankline.nvim/plugin/indent_blankline.vim
</span></span><span style="display:flex;"><span> 0.549500  0.789000  0.514000: init highlight</span></span></code></pre></div></p>
<p>The snippet infrastructure is now absent from that list! More importantly, the overall startup time is down, and we can be confident that the new calls to load the snippets in <code>init.lua</code> are not costlier than UltiSnips settings before, because the <code>init.lua</code> line is down as well.</p>
<p>As a bonus, the snippet expansion feels slightly snappier with LuaSnip, although it might be an illusion and I don’t have hard numbers to back this claim.</p>
<h2 id="migrate-my-snippet-collection">Migrate my Snippet Collection</h2>
<p>Back to the topic of migrating existing UltiSnips snippets: LuaSnip will loudly complain when given UltiSnips snippets but it’s relatively easy to rewrite those snippets to the SnipMate-like format that LuaSnip understands.</p>
<h3 id="two-syntaxes">Two Syntaxes</h3>
<p>On the one hand, UltiSnips snippets roughly follow this syntax</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet trigger &#34;Comment&#34; option
snippet content
endsnippet
</code></pre><p>And so, my markdown snippets would look like this:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">priority 10

snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>On the other hand, LuaSnip uses a simplified version of SnipMate snippets:</p>
<pre tabindex="0"><code># Comment
snippet toggle
  snippet content
</code></pre><h3 id="simple-snippets">Simple Snippets</h3>
<p>Since I heavily rely on snippet sets, I have only about 30 snippets defined in my own snippet collection. Most of them are really simple, with only a few lines and almost no interactive text. So for most of those, the process has been to simply remove the <code>priority …</code> lines and then a simple substitution command<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> did the trick.</p>
<h3 id="advanced-snippets-with-environment-variables">Advanced Snippets With Environment Variables</h3>
<p>However, there is also the case of the snippets calling external commands, like <code>date</code> in the example below:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet">snippet bjtoday &#34;“Bullet Journal”-styled date for today&#34; b
# `date +&#39;%F %A&#39;`
endsnippet
</code></pre><p>The problem is, to call arbitrary commands, one needs to define the snippets in Lua.</p>
<p>It turns out though that nearly all my snippets calling external command were actually inserting a date. And luckily LuaSnip defines “environment variables” holding just the values I need like <code>$CURRENT_MONTH</code>. So that snippet becomes:</p>
<pre tabindex="0"><code class="language-snippet" data-lang="snippet"># “Bullet Journal”-styled date for today
snippet bjtoday
  # ${CURRENT_YEAR}-${CURRENT_MONTH}-${CURRENT_DATE} $CURRENT_DAY_NAME_SHORT
</code></pre><p>and we get to keep the simple SnipMate-like syntax!</p>
<p>You can find more of those environment variables in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/lua/luasnip/util/environ.lua">the sources</a>.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-08-03: See also <a href="https://github.com/smjonas/snippet-converter.nvim">snippet-converter.nvim</a> to convert between various snippet formats.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup></p>
  </div>



<h2 id="whats-next">What’s Next</h2>
<p>I now have a slightly faster NeoVim and the snippet syntax that I use the most is simpler. However, there is more to explore!</p>
<p>So far, I’ve steered clear of writing more complicated JSON and Lua snippets. The latter would be necessary to unlock smart snippets using treesitter <a href="https://changelog.com/podcast/457#t=01:00:01.17">context</a>. I’ll look into that next, in particular to generate go error handling code.</p>
<p>This is also a very simple configuration, the impact on startup time of LuaSnip might go up slightly as we use more advanced feature, but during my tests, even using all available features, it was much lighter than UltiSnips.</p>




  
  
  
  

  <div class="alert alert-edit">
    <p class="alert-heading">
      ✏
      
        Edit
      
    </p>
    <p>2022-07-31: <a href="https://cj.rs/tags/luasnip/">Follow-up posts</a> dig deeper in other aspects of LuaSnip</p>
  </div>



<h2 id="resources">Resources</h2>
<ul>
<li>Many more details are covered in <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md">LuaSnip documentation</a>.</li>
<li>The <a href="https://github.com/garbas/vim-snipmate/blob/master/doc/SnipMate.txt">SnipMate help</a> contains the SnipMate snippet syntax, if you are unfamiliar with it.</li>
<li>The <a href="https://github.com/microsoft/language-server-protocol/blob/main/snippetSyntax.md">LSP snippet documentation</a> is also helpful.</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>That run is actually on a fork of <a href="https://github.com/navarasu/onedark.nvim">onedark.nvim</a> that contains a massive speed up. The changes of that fork are being up streamed in <a href="https://github.com/navarasu/onedark.nvim/pull/76">this PR</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>According to that comment from the author for instance: <a href="https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664">https://github.com/L3MON4D3/LuaSnip/issues/60#issuecomment-873630664</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>This code snippet uses new APIs introduced in Neovim 0.7 and the newly freed <code>&lt;C-i&gt;</code>, see <a href="https://gpanders.com/blog/whats-new-in-neovim-0-7/">https://gpanders.com/blog/whats-new-in-neovim-0-7/</a> for more details.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>For instance: <code>%s/^\(snippet \+\S\+\) &quot;\(.*\)&quot; \w\+\n\(\_.\{-}\)endsnippet/# \2^M\1^M \3^M</code>.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Thanks to <a href="https://www.reddit.com/user/Miserable-Ad-7341">Miserable-Ad-7341</a> for <a href="https://www.reddit.com/r/neovim/comments/weonip/from_ultisnips_to_luasnip/iipheov/">pointing this out</a>.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Git ls-files is Faster Than Fd and Find</title><link>https://cj.rs/blog/git-ls-files-is-faster-than-fd-and-find/</link><pubDate>Thu, 04 Nov 2021 06:06:21 +0000</pubDate><guid>https://cj.rs/blog/git-ls-files-is-faster-than-fd-and-find/</guid><description>Git ls-files is up to 5 times faster than fd or find in this benchmark, but why?</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>In the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/">Linux Git repository</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/tldr.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.9 ± 0.5</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">18.2</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.1 ± 0.7</td>
          <td style="text-align: right">92.4</td>
          <td style="text-align: right">95.7</td>
          <td style="text-align: right">5.52 ± 0.16</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">85.8 ± 7.5</td>
          <td style="text-align: right">81.1</td>
          <td style="text-align: right">111.3</td>
          <td style="text-align: right">5.08 ± 0.47</td>
      </tr>
  </tbody>
</table>
<p><code>git ls-files</code> is more than <em>5 times faster</em> than both <code>fd --no-ignore</code> and <code>find</code>!</p>
  </div>



<h2 id="introduction">Introduction</h2>
<p>In my <a href="https://cj.rs/blog/my-setup/nvim-0-5/">editor</a> I changed my mapping to open files from <code>fd</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> to <code>git ls-files</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> and I noticed it felt faster after the change. But that’s intriguing, given <code>fd</code>’s goal to be <a href="https://github.com/sharkdp/fd#benchmark">very fast</a>. Git on the other hand is primarily a source code management system (SCM), it’s main business<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> is not to help you list your files! Let’s run some benchmarks to make sure.</p>
<h2 id="benchmarks">Benchmarks</h2>
<p>Is <code>git ls-files</code> actually faster than <code>fd</code> or is that just an illusion? In our benchmark, we will use:</p>
<ul>
<li><code>fd</code> 8.2.1</li>
<li><code>git</code> 2.33.0</li>
<li><code>find</code> 4.8.0</li>
<li><code>hyperfine</code> 1.11.0</li>
</ul>
<p>We run the benchmarks with disk-cache filled, we are not measuring the cold cache case. That’s because in your editor, you may use the commands mentioned multiple times and would benefit from cache. The results are similar for an in memory repo, which confirms cache filling.</p>
<p>Also, you work on those files, so they should be in cache to a degree. We also make sure to be on a quiet PC, with CPU power-saving deactivated. Furthermore, the CPU has 8 cores with hyper-threading, so <code>fd</code> uses 8 threads. Last but not least, unless otherwise noted, the files in the repo are only the ones committed, for instance, no build artifacts are present.</p>
<h3 id="a-test-git-repository">A Test Git Repository</h3>
<p>We first need a Git repository. I’ve chosen to clone<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/">Linux kernel repo</a> because it is a fairly big one and a <a href="https://github.blog/2020-12-22-git-clone-a-data-driven-study-on-cloning-behaviors/">reference</a> for Git performance measurements. This is important to ensure searches take a non-trivial amount of time: as hyperfine rightfully points out, short run times (less than 5 ms) are more difficult to accurately compare.</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">git</span> clone <span style="color:#e06c75">--depth</span> <span style="color:#d19a66">1</span> <span style="color:#e06c75">--recursive</span> ssh://git@github.com/torvalds/linux.git ~/ghq/github.com/torvalds/linux
</span></span><span style="display:flex;"><span><span style="color:#c678dd">cd</span> ~/ghq/github.com/torvalds/linux
</span></span></code></pre></div><h4 id="choosing-the-commands">Choosing the commands</h4>
<p>We want to evaluate <code>git ls-files</code> versus <code>fd</code> and <code>find</code>. However, getting exactly the same list of file is not a trivial task:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Output lines</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">72219</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">77039</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">76705</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden</code></td>
          <td style="text-align: right">77038</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd</code></td>
          <td style="text-align: right">72363</td>
      </tr>
  </tbody>
</table>
<p>After some more tries, it turns out that this command gives exactly<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> the same output as <code>git ls-files</code>:</p>
<pre tabindex="0"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink
</code></pre><p>It is a fairly complicated command, with various criteria on the files to print and that could translate to an unfair advantage to <code>git ls-files</code>. Consequently, we will also use the simpler examples in the table above.</p>
<h3 id="hyperfine">Hyperfine</h3>
<p><a href="https://github.com/sharkdp/hyperfine">Hyperfine</a> is a great tool to compare various commands: it has a colored and markdown output, attempts to detect outliers, tunes the number of run… Here is an <a href="https://asciinema.org/">asciinema</a><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> showing its output<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>:</p>
<div id="demo3"></div>
<script>
AsciinemaPlayer.create("/blog/git-ls-files-is-faster-than-fd-and-find/hyperfine.json", document.getElementById('demo3'), {
"cols": "103","loop": "true","preload":  1 ,"rows": "32","speed": "4",
});
</script>
<noscript><blockquote><p>To run this asciicast without javascript, use <code>asciinema play https://cj.rs/blog/git-ls-files-is-faster-than-fd-and-find/hyperfine.json</code> with <a href="https://asciinema.org/">Asciinema</a></p></blockquote></noscript>

<h3 id="first-results">First Results</h3>
<p>For our first benchmark, on an SSD with <a href="https://en.wikipedia.org/wiki/Btrfs">btrfs</a>, with commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=ad347abe4a9876b1f65f408ab467137e88f77eb4"><code>ad347abe4a…</code></a> checked out, we run:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">1</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden&#39;</span> <span style="color:#98c379">&#39;fd&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>    <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><p>This yields the following results:</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.9 ± 0.6</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">19.2</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.2 ± 0.5</td>
          <td style="text-align: right">92.5</td>
          <td style="text-align: right">94.8</td>
          <td style="text-align: right">5.50 ± 0.19</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.6 ± 7.8</td>
          <td style="text-align: right">80.5</td>
          <td style="text-align: right">115.7</td>
          <td style="text-align: right">5.11 ± 0.49</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden</code></td>
          <td style="text-align: right">121.0 ± 6.2</td>
          <td style="text-align: right">112.3</td>
          <td style="text-align: right">132.3</td>
          <td style="text-align: right">7.14 ± 0.44</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd</code></td>
          <td style="text-align: right">231.6 ± 22.3</td>
          <td style="text-align: right">200.8</td>
          <td style="text-align: right">272.5</td>
          <td style="text-align: right">13.68 ± 1.40</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.9 ± 5.0</td>
          <td style="text-align: right">77.5</td>
          <td style="text-align: right">95.3</td>
          <td style="text-align: right">4.78 ± 0.34</td>
      </tr>
  </tbody>
</table>
<p>As mentioned in the TL;DR, <code>git ls-files</code> is at least 5 times faster than its closest competitor! Let’s find out why that is.</p>
<h2 id="how-does-git-store-files-in-a-repository">How Does Git Store Files in a Repository</h2>
<p>To try to understand where this performance advantage of <code>git ls-files</code> comes from, let’s look into how files are stored in a repository. This is a quick overview, you can find more details about Git’s storage internals in <a href="https://git-scm.com/book/en/v2/Git-Internals-Git-Objects">this section of the Pro Git book</a>.</p>
<h3 id="git-objects">Git Objects</h3>
<p>Git builds its own internal representation of the file system tree in the repository:</p>
<figure>
    <img loading="lazy" src="./data-model-2.png"
         alt="Internal Git representation of the file system tree" width="800" height="593"/> <figcaption>
            Internal Git representation of the file system tree<p>From the Pro Git book, written by Scott Chacon and Ben Straub and published by Apress, licensed under the <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution Non Commercial Share Alike 3.0</a> license, copyright 2021.</p>
        </figcaption>
</figure>

<p>In the figure above, each tree object contains a list of folder or names and references to these (among other things). This representation is then stored by its hash in the <code>.git</code> folder, like so:</p>
<pre tabindex="0"><code>.git/objects
├── 65
│  └── 107a3367b67e7a50788f575f73f70a1e61c1df
├── e6
│  └── 9de29bb2d1d6434b8b29ae775ad8c2e48c5391
├── f0
│  └── f1a67ce36d6d87e09ea711c62e88b135b60411
├── info
└── pack
</code></pre><p>As a result, to list the content of a folder, it seems Git has to access the corresponding tree object, stored in a file contained in a folder with the beginning of the hash. But doing that for the currently checked out files all the time would be slow, especially for frequently used commands like <code>git status</code>. Fortunately, git also maintains an <em>index</em> for files in the current working directory.</p>
<h3 id="git-index">Git Index</h3>
<p>This <a href="https://git-scm.com/docs/index-format">index</a>, lists (among other things) each file in the repository with file-system metadata like last modification time. More details and examples are provided <a href="https://medium.com/hackernoon/understanding-git-index-4821a0765cf">here</a>.</p>
<p>So, it seems that the index has everything <code>ls-files</code> requires. Let’s check it is used by <code>ls-files</code></p>
<h2 id="strace">Strace</h2>
<p>Let’s ensure that <code>ls-files</code> uses only the index, without scanning many files in the repo or the <code>.git</code> folder. That would explain its performance advantage, as reading a file is cheaper than traversing many folders. To this end, we’ll use <code>strace</code><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> like so:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">strace</span> <span style="color:#e06c75">-e</span> !write git ls-files<span style="color:#56b6c2">&gt;</span>/dev/null <span style="color:#d19a66">2</span><span style="color:#56b6c2">&gt;</span>/tmp/a
</span></span></code></pre></div><p>It turns out the <a href="https://git-scm.com/docs/index-format"><code>.git/index</code></a> is read:</p>
<pre tabindex="0"><code>openat(AT_FDCWD, &#34;.git/index&#34;, O_RDONLY) = 3
</code></pre><p>And we are not reading objects in the <code>.git</code> folder or files in the repository. A quick check of Git’s source code <a href="https://github.com/git/git/blob/33be431c0c7284c1adf0fe49f7838dbc8aee6ea9/builtin/ls-files.c#L761">confirms</a> this. We now have an explanation for the speed <code>git ls-files</code> displays in our benchmarks!</p>
<h2 id="other-scenarios">Other Scenarios</h2>
<p>However, listing file in a fully committed repository is not the most common case when you work on your code: as you make changes, a larger portion of the files are changed or added. How does <code>git ls-files</code> compare in these other scenarios?</p>
<h3 id="with-changes">With Changes</h3>
<p>When there are changes to some files, we shouldn’t see any significant performance difference: the index is still usable directly to get the names of the files in the repository, we don’t really care about whether their content changed.</p>
<p>To check this, let’s change all the C files in the kernel sources (using some <a href="https://fishshell.com/">fish</a> shell scripting):</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#c678dd">for</span> <span style="color:#e06c75">f</span> <span style="color:#c678dd">in</span> <span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">fd</span> <span style="color:#e06c75">-e</span> c<span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#c678dd">echo</span> <span style="color:#d19a66">1</span> <span style="color:#56b6c2">&gt;&gt;</span> <span style="color:#e06c75">$f</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">git</span> <span style="color:#e5c07b">status</span> <span style="color:#56b6c2">|</span> <span style="color:#61afef;font-weight:bold">wc</span> <span style="color:#e06c75">-l</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">28350</span>
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">2</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.8 ± 0.5</td>
          <td style="text-align: right">16.3</td>
          <td style="text-align: right">18.9</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">93.5 ± 0.7</td>
          <td style="text-align: right">92.7</td>
          <td style="text-align: right">95.5</td>
          <td style="text-align: right">5.55 ± 0.17</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.1 ± 7.3</td>
          <td style="text-align: right">80.9</td>
          <td style="text-align: right">112.6</td>
          <td style="text-align: right">5.12 ± 0.46</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.8 ± 6.6</td>
          <td style="text-align: right">77.8</td>
          <td style="text-align: right">115.0</td>
          <td style="text-align: right">4.80 ± 0.42</td>
      </tr>
  </tbody>
</table>
<p>We see the same numbers as before and it is again consistent with <a href="https://github.com/git/git/blob/33be431c0c7284c1adf0fe49f7838dbc8aee6ea9/builtin/ls-files.c#L761">ls-files source code</a>.</p>
<p>Run <code>git checkout -f @</code> after this to remove the changes made to the files.</p>
<h3 id="with-new-files-and--o">With New Files and <code>-o</code></h3>
<p>With yet uncommitted files, there are two subcases:</p>
<ul>
<li>files were created and added (with <code>git add</code>): then the files are in index and reading the index is enough for <code>ls-files</code>, like above,</li>
<li>files were created but not added: these files are not present in the index, but without the <code>-o</code> flag, <code>ls-files</code> won’t output them either, so it can still use the index, as before.</li>
</ul>
<p>So the only case that needs further investigations is the use of <code>-o</code>. Since we don’t have baseline results yet for <code>-o</code>, let’s first see how it compares without any unadded new files.</p>
<h4 id="without-any-unadded-new-files-baseline">Without any Unadded New Files (Baseline)</h4>
<p>When we haven’t added any new files in the repository:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">3</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;git ls-files -o&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.7 ± 0.5</td>
          <td style="text-align: right">16.1</td>
          <td style="text-align: right">17.9</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>git ls-files -o</code></td>
          <td style="text-align: right">69.1 ± 0.7</td>
          <td style="text-align: right">67.8</td>
          <td style="text-align: right">70.8</td>
          <td style="text-align: right">4.12 ± 0.12</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">94.3 ± 0.5</td>
          <td style="text-align: right">93.4</td>
          <td style="text-align: right">95.3</td>
          <td style="text-align: right">5.63 ± 0.16</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.6 ± 7.0</td>
          <td style="text-align: right">80.8</td>
          <td style="text-align: right">106.0</td>
          <td style="text-align: right">5.17 ± 0.44</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">80.8 ± 7.4</td>
          <td style="text-align: right">77.9</td>
          <td style="text-align: right">118.0</td>
          <td style="text-align: right">4.82 ± 0.46</td>
      </tr>
  </tbody>
</table>
<p>That suggests that <code>git ls-files -o</code> is performing some more work besides “just” reading the index. With <code>strace</code>, we see lines like:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">strace</span> <span style="color:#e06c75">-e</span> !write git ls-files <span style="color:#e06c75">-o</span><span style="color:#56b6c2">&gt;</span>/dev/null <span style="color:#d19a66">2</span><span style="color:#56b6c2">&gt;</span>/tmp/a
</span></span><span style="display:flex;"><span>…
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">openat</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">AT_FDCWD</span>, <span style="color:#98c379">&#34;Documentation/&#34;</span>, O_RDONLY<span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_NONBLOCK</span><span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_CLOEXEC</span><span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">O_DIRECTORY</span><span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">4</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">newfstatat</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">4</span>, <span style="color:#98c379">&#34;&#34;</span>, <span style="color:#56b6c2">{</span><span style="color:#e06c75">st_mode</span><span style="color:#56b6c2">=</span>S_IFDIR<span style="color:#56b6c2">|</span><span style="color:#61afef;font-weight:bold">0755</span>, <span style="color:#e06c75">st_size</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">1446</span>, ...<span style="color:#56b6c2">}</span>, AT_EMPTY_PATH<span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">0</span>
</span></span><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">getdents64</span><span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">4</span>, 0x55df0a6e6890 /* <span style="color:#d19a66">99</span> entries */, <span style="color:#d19a66">32768</span><span style="color:#56b6c2">)</span> <span style="color:#56b6c2">=</span> <span style="color:#d19a66">3032</span>
</span></span><span style="display:flex;"><span>…
</span></span></code></pre></div><h4 id="with-unadded-new-files">With Unadded New Files</h4>
<p>Let’s add some files now:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#c678dd">for</span> <span style="color:#e06c75">f</span> <span style="color:#c678dd">in</span> <span style="color:#56b6c2">(</span><span style="color:#61afef;font-weight:bold">seq</span> <span style="color:#d19a66">1</span> <span style="color:#d19a66">1000</span><span style="color:#56b6c2">)</span>
</span></span><span style="display:flex;"><span>  <span style="color:#61afef;font-weight:bold">touch</span> <span style="color:#e06c75">$f</span>
</span></span><span style="display:flex;"><span><span style="color:#c678dd">end</span>
</span></span></code></pre></div><p>And compare with our baseline:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-fish" data-lang="fish"><span style="display:flex;"><span><span style="color:#61afef;font-weight:bold">hyperfine</span> <span style="color:#e06c75">--export-markdown</span> /tmp/<span style="color:#d19a66">4</span>.md <span style="color:#e06c75">--warmup</span> <span style="color:#d19a66">10</span> <span style="color:#98c379">&#39;git ls-files&#39;</span> <span style="color:#98c379">&#39;git ls-files -o&#39;</span> <span style="color:#98c379">&#39;find&#39;</span> <span style="color:#98c379">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#98c379">&#39;fd --no-ignore&#39;</span> <span style="color:#98c379">&#39;fd --no-ignore --hidden --exclude .git --type file --type symlink&#39;</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th style="text-align: left">Command</th>
          <th style="text-align: right">Mean [ms]</th>
          <th style="text-align: right">Min [ms]</th>
          <th style="text-align: right">Max [ms]</th>
          <th style="text-align: right">Relative</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left"><code>git ls-files</code></td>
          <td style="text-align: right">16.8 ± 0.5</td>
          <td style="text-align: right">16.1</td>
          <td style="text-align: right">18.0</td>
          <td style="text-align: right">1.00</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>git ls-files -o</code></td>
          <td style="text-align: right">69.9 ± 1.2</td>
          <td style="text-align: right">68.1</td>
          <td style="text-align: right">72.6</td>
          <td style="text-align: right">4.17 ± 0.14</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>find</code></td>
          <td style="text-align: right">94.5 ± 0.6</td>
          <td style="text-align: right">93.4</td>
          <td style="text-align: right">96.3</td>
          <td style="text-align: right">5.64 ± 0.17</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore</code></td>
          <td style="text-align: right">86.8 ± 7.5</td>
          <td style="text-align: right">81.5</td>
          <td style="text-align: right">114.4</td>
          <td style="text-align: right">5.18 ± 0.48</td>
      </tr>
      <tr>
          <td style="text-align: left"><code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code></td>
          <td style="text-align: right">81.0 ± 4.5</td>
          <td style="text-align: right">78.6</td>
          <td style="text-align: right">96.3</td>
          <td style="text-align: right">4.83 ± 0.31</td>
      </tr>
  </tbody>
</table>
<p>There is little to no statically significant difference to our baseline, which highlights that much of the time is spent on things relatively independent of the number of files processed. It’s also worth noting that there is relatively little speed difference between <code>git ls-files -o</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code>.</p>
<p>Using <code>strace</code>, we can establish that all commands but <code>git ls-files</code> were reading all files in the repository. By comparing the <code>strace</code> outputs of <code>git ls-files -o</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code> (the two commands that print the same file list), we can see that they make similar system calls for each file in the repository. How to explain the (small) time difference between the two? I haven’t found convincing reasons in git source code for this case. It might be that the use of the <code>index</code> gives <code>ls-files</code> a head start.</p>
<h2 id="conclusions">Conclusions</h2>
<p>I’m now using <code>git ls-files</code> in my <a href="https://cj.rs/blog/my-setup/nvim-0-5/">keyboard driven text editor</a> instead of <code>fd</code> or <code>find</code>. It is faster, although the perceived difference described in the Introduction is probably due to spikes in latency on a cold cache. The selection of files is also narrowed down with <code>ls-files</code> to the ones I care about. That’s said, I’ve still kept the <code>fd</code>-based file listing as a fallback, as sometimes I’m not in a Git repository.</p>
<p>After all, Git is already building an index, why not use it to speed up your jumping from file to file!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>With <a href="https://github.com/nvim-telescope/telescope.nvim">Telescope.nvim</a> <code>:Telescope find_files</code>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>With <a href="https://github.com/nvim-telescope/telescope.nvim">Telescope.nvim</a> <code>:Telescope git_files show_untracked=false</code>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>That’s not to say git is slow, on the contrary, when one reads the release notes, it’s obvious that a lot of performance optimization work is done.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Using a shallow clone makes it faster for you to reproduce results locally. However, running the benchmarks again on a full clone does not significantly change the results.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Using the <code>diff</code> command on the outputs of <code>git ls-files</code> and <code>fd --no-ignore --hidden --exclude .git --type file --type symlink</code>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>This is inserted in this page using my <a href="https://cj.rs/gohugo-asciinema/?ref=git-faster-fd-find">asciinema hugo module</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>This output has been edited to remove the warning about outliers. These warning appeared only with <code>asciinema</code>, probably because it is disturbing the benchmark. This also explains why the values in this “asciicast” are different from the tables in the rest of the article: I’ve used values from runs outside asciinema for these tables.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>See also <a href="https://jvns.ca/blog/2014/04/20/debug-your-programs-like-theyre-closed-source/">https://jvns.ca/blog/2014/04/20/debug-your-programs-like-theyre-closed-source/</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Putting READMEs on your Static Site</title><link>https://cj.rs/blog/putting-readmes-on-your-static-site/</link><pubDate>Thu, 23 Sep 2021 20:45:11 +0000</pubDate><guid>https://cj.rs/blog/putting-readmes-on-your-static-site/</guid><description>I’m happy to introduce RISS (README In Static Site), a tool that transforms a README file and insert it on your website</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <p>I’m happy to introduce <a href="https://cj.rs/readme-in-static-site/">RISS</a> (<em>README In Static Site</em>), a tool that transforms a README like <a href="https://github.com/cljoly/telescope-repo.nvim">this one</a> and insert it on your website, <a href="https://cj.rs/telescope-repo-nvim/">like so</a>.</p>
  </div>



<figure>
    <img loading="lazy" src="./riss-in-action.png"
         alt="A screenshot of two browser windows, showing RISS in action. On the left hand side, a project page on GitHub and on the right hand side, its website. Both share the same core content."/> <figcaption>
            <p><a href="https://cj.rs/telescope-repo-nvim/">Telescope-repo.nvim</a> GitHub page and website, automatically generated from the Readme code.</p>
        </figcaption>
</figure>

<h2 id="a-readme-is-a-projects-cover">A README is a Project’s Cover</h2>
<p>Your GitHub README is what your visitors on GitHub will encounter first. Thus, it needs to efficiently describe your project. There are a lot of <a href="https://github.com/zenika-open-source/promote-open-source-project#readme">advice online</a> on how to craft a great README.</p>
<p>So you then go on and create a great README. Wonderful! 🎉</p>
<h2 id="but-i-want-the-readme-on-my-website">But I want the README on my Website</h2>
<p>But then, you want to include your README on your static website, for instance in a <a href="https://dev.to/lornasw93/github-readme-on-portfolio-project-page-51i8">portfolio</a> or just to have a GitHub-independent homepage. You also want more control on the layout and theme than GitHub has to offer, and perhaps even <a href="https://matomo.org/">privacy</a>-<a href="https://plausible.io/">preserving</a> <a href="https://www.goatcounter.com/">analytics</a>.
This is a <a href="https://stackoverflow.com/q/16226202/4253785">common</a> <a href="https://stackoverflow.com/q/48919200/4253785">question</a> and there are options based on <a href="https://richjenks.com/github-pages-from-readme/">ajax</a> or on one <a href="https://medium.com/@bolajiayodeji/how-to-convert-github-markdown-files-to-a-simple-website-b08602d05e1c">GitHub Pages</a> website per repository.</p>
<p>However, dynamically loading the README on the client side sounds like taking a performance hit for what is actually static content. Furthermore, maintaining one full-blown GitHub Pages static website  for each small project, might not be worth it.</p>
<p>What if you could just change your README a bit so that it is ready for inclusion in your <a href="https://www.11ty.dev/">Eleventy</a> or <a href="https://gohugo.io/">Hugo</a> personal website, just like any other markdown page?</p>
<h2 id="enter-riss">Enter <a href="https://cj.rs/readme-in-static-site/">RISS</a>!</h2>
<p>I wrote <a href="https://cj.rs/readme-in-static-site/">RISS</a> (<em>README In Static Site</em>) for this very purpose.</p>
<p>With RISS, your project’s README is the source of truth. You just add simple comments for the small bits that need to differ between GitHub and your website. You then give the GitHub README to RISS as an input and the output is a file suitable for inclusion in your static site sources. As your README change and evolves, updating it is easy, you just rerun the script.</p>
<p>Let’s go over some examples!</p>
<h3 id="post-metadata">Post Metadata</h3>
<p>Hugo (and other static-site generators) need some <a href="https://gohugo.io/content-management/front-matter">metadata</a> at the start of your post, like so:</p>
<pre tabindex="0"><code>---
title: &#34;README In Static Site (RISS)&#34;
date: 2021-08-21T08:15:54
description: &#34;💎 Transform and insert your GitHub README in your static site&#34;
---
</code></pre><p>Of course, we don’t want to have this on GitHub. We thus put it in a comment, so that it remains hidden on GitHub:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-html" data-lang="html"><span style="display:flex;"><span><span style="color:#7f848e">&lt;!-- insert
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">---
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">title: &#34;README In Static Site (RISS)&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">date: 2021-08-21T08:15:54
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">description: &#34;💎 Transform and insert your GitHub readme in your static site&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">---
</span></span></span><span style="display:flex;"><span><span style="color:#7f848e">end_insert --&gt;</span>
</span></span></code></pre></div><p>given the above comment as input, RISS outputs:</p>
<pre tabindex="0"><code>---
title: &#34;README In Static Site (RISS)&#34;
date: 2021-08-21T08:15:54
description: &#34;💎 Transform and insert your GitHub README in your static site&#34;
---
</code></pre><p>and the resulting file is usable by Hugo!</p>
<h2 id="more">More</h2>
<p>GitHub prohibits embedding arbitrary scripts for security reason. But what if you want to embed an <a href="https://asciinema.org/">asciinema</a> player on your project homepage, so that users can play the asciicast as they would a standard video?
You can have a placeholder with the link on GitHub as asciinema documentation advises and then use RISS to replace it with the full player on your website. You will find how to do that in the <a href="https://cj.rs/readme-in-static-site/">documentation</a>. Plus, here is an example README <a href="https://github.com/cljoly/telescope-repo.nvim">this one</a> doing this (and the <a href="https://cj.rs/telescope-repo-nvim/">corresponding website</a>, look for the asciicast that autoplays).</p>
<p>See also how to <a href="https://cj.rs/readme-in-static-site/">automate</a> the update of all these READMEs with, for instance, GitHub Action.</p>
<p>Thank you for reading!</p>
]]></content:encoded></item><item><title>Academic Papers 2021</title><link>https://cj.rs/blog/academic-papers-2021/</link><pubDate>Sat, 21 Aug 2021 20:18:46 +0000</pubDate><guid>https://cj.rs/blog/academic-papers-2021/</guid><description>&lt;p&gt;Here is some interesting papers I’ve read this year.&lt;/p&gt;
&lt;h2 id="weaponizing-middleboxes-for-tcp-reflected-amplification"&gt;Weaponizing Middleboxes for TCP Reflected Amplification&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.usenix.org/system/files/sec21-bock.pdf"&gt;https://www.usenix.org/system/files/sec21-bock.pdf&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This paper uses the fact that some middleboxes are non-compliant TCP stacks. In particular, middleboxes sometimes see only one side of a connection and as a result, it’s possible that they answer spoofed packets. After an initial training (with a genetic algorithm) on a subset of known censoring networks to optimize amplification factors, the authors present the results of applying this to the whole IPv4 internet.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Here is some interesting papers I’ve read this year.</p>
<h2 id="weaponizing-middleboxes-for-tcp-reflected-amplification">Weaponizing Middleboxes for TCP Reflected Amplification</h2>
<p><a href="https://www.usenix.org/system/files/sec21-bock.pdf">https://www.usenix.org/system/files/sec21-bock.pdf</a></p>
<p>This paper uses the fact that some middleboxes are non-compliant TCP stacks. In particular, middleboxes sometimes see only one side of a connection and as a result, it’s possible that they answer spoofed packets. After an initial training (with a genetic algorithm) on a subset of known censoring networks to optimize amplification factors, the authors present the results of applying this to the whole IPv4 internet.</p>
<h2 id="xor-filters-faster-and-smaller-than-bloom-and-cuckoo-filters">Xor Filters: Faster and Smaller Than Bloom and Cuckoo Filters</h2>
<p><a href="https://arxiv.org/abs/1912.08258">https://arxiv.org/abs/1912.08258</a></p>
<p>A bit like bloom filters, but read-only, more compact and faster. The companion blog post is a good introduction: <a href="https://lemire.me/blog/2019/12/19/xor-filters-faster-and-smaller-than-bloom-filters/">https://lemire.me/blog/2019/12/19/xor-filters-faster-and-smaller-than-bloom-filters/</a>.</p>
<h2 id="to-be-continued">To be continued</h2>
<p>I’ll update this page through 2021 if I want to share other papers. In the meantime, here are other posts listing papers:</p>
<ul>
<li><a href="https://ordep.dev/posts/my-favorite-papers">https://ordep.dev/posts/my-favorite-papers</a></li>
<li><a href="https://github.com/papers-we-love/papers-we-love">https://github.com/papers-we-love/papers-we-love</a></li>
<li><a href="https://github.com/facundoolano/software-papers">https://github.com/facundoolano/software-papers</a></li>
</ul>
]]></content:encoded></item><item><title>SQLite Pragma Cheatsheet for Performance and Consistency</title><link>https://cj.rs/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/</link><pubDate>Fri, 07 May 2021 17:33:23 +0000</pubDate><guid>https://cj.rs/blog/sqlite-pragma-cheatsheet-for-performance-and-consistency/</guid><description>Last updated: March 18, 2024.</description><content:encoded><![CDATA[



  
  
  
  

  <div class="alert alert-tldr">
    <p class="alert-heading">
      ⚡
      
        TL;DR
      
    </p>
    <h3 id="when-opening-the-db">When Opening the DB</h3>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">journal_mode</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">wal</span>; <span style="color:#7f848e">-- different implementation of the atomicity properties
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">synchronous</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">normal</span>; <span style="color:#7f848e">-- synchronise less often to the filesystem
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">foreign_keys</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">on</span>; <span style="color:#7f848e">-- check foreign key reference, slightly worst performance
</span></span></span></code></pre></div><p>And check <code>user_version</code> to apply any migrations, for instance with this <a href="https://cj.rs/rusqlite_migration/">Rust library</a>.</p>
<h3 id="when-closing-the-db">When Closing the DB</h3>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">analysis_limit</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">400</span>; <span style="color:#7f848e">-- make sure pragma optimize does not take too long
</span></span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">optimize</span>; <span style="color:#7f848e">-- gather statistics to improve query optimization
</span></span></span></code></pre></div>
  </div>



<h2 id="introduction">Introduction</h2>
<p>SQL pragma are statements (like <code>SELECT …</code> or <code>CREATE TABLE …</code>) that change the database behaviors or call a special functions. This post is a short list of <a href="https://sqlite.org">SQLite</a> pragma I use in my projects built on SQLite, to get better performance and more consistency.</p>
<h2 id="performance">Performance</h2>
<h3 id="file-system-interactions">File system Interactions</h3>
<p>The following pragma statements set the way SQLite deals with the file system.</p>
<h4 id="journal_mode-wal">journal_mode wal</h4>
<p>By default, when applying changes, SQLite uses a rollback journal, which is basically a copy of the data before the changes. Changing the <a href="https://sqlite.org/pragma.html#pragma_journal_mode">journal mode</a> to “Write-Ahead Log” is known to bring significantly better performance in most cases. It also allows concurrent readers with one writer. To activate it, run:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">journal_mode</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">wal</span>;
</span></span></code></pre></div><p>Some less common use cases are incompatible with this journal mode, for instance having a database on a network file system. The full list of drawbacks is <a href="https://sqlite.org/wal.html">listed in the documentation</a><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<h4 id="synchronous-normal">synchronous normal</h4>
<p>To ensure integrity of the database, one of the mechanism SQLite uses is the file system synchronization operations. However, these synchronizations are quite costly. With WAL journal mode enabled, your database will still be consistent while synchronizing less often:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">synchronous</span> <span style="color:#56b6c2">=</span> <span style="color:#e06c75">normal</span>;
</span></span></code></pre></div><p>Compared to the default <a href="https://sqlite.org/pragma.html#pragma_synchronous"><code>synchronous = full</code></a>, committed transactions could be rolled back if there is a power loss (although not if the application crashes).</p>
<h3 id="optimize">Optimize</h3>
<p>To execute statement as efficiently as possible, SQLite has a <a href="https://sqlite.org/queryplanner.html">query planner</a>, which tries to read the tables to provide good performance (for instance by evaluating the <code>WHERE</code> clauses that select the fewest rows first).</p>
<p>This query planner sometimes needs to know whether a column has many values or only a few, repeated values (like a boolean column would). To know this, it can’t read the whole table, as that may prove as costly as running the part of the query that is being optimized, so it uses some statistics collected in internal tables.</p>
<p>Using <a href="https://sqlite.org/pragma.html#pragma_optimize">optimize</a> right before closing the database connexion collects the statistics for some table columns. These columns are chosen mainly based on the queries executed during the connexion: if a query had benefited from more accurate statistics, the corresponding columns are analyzed.</p>
<p>For instance:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">analysis_limit</span><span style="color:#56b6c2">=</span><span style="color:#d19a66">400</span>;
</span></span><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">optimize</span>;
</span></span></code></pre></div><p>In the above example, pragma <a href="https://sqlite.org/pragma.html#pragma_analysis_limit">analysis_limit</a> ensures that <code>optimize</code> won’t run for too long, by limiting the number of rows read.</p>
<h3 id="allow-using-more-memory">Allow Using More Memory</h3>
<p>These two pragma <em>could</em> result in better performance, depending on your hardware and software configuration.</p>
<ul>
<li>Keep temporary storage in memory: <a href="https://sqlite.org/pragma.html#pragma_temp_store"><code>PRAGMA temp_store = 2</code></a>. However, setting this pragma does not guarantee that the temporary storage will be held in memory. Please refer to the documentation for other parameters that may change the final outcome.</li>
<li>Keep more of the database pages in memory: for instance, use 32 MiB of memory for this purpose with <a href="https://sqlite.org/pragma.html#pragma_cache_size"><code>PRAGMA cache_size = -32000</code></a>. Note that the OS cache is already keeping parts of the database file in RAM, so this might end up wasting memory.</li>
</ul>
<h2 id="consistency">Consistency</h2>
<h3 id="user_version">user_version</h3>
<p>The database&rsquo;s schema can evolve over time. Your application could start with a <code>car</code> table to represent a car but later on, you realize you want to represent bicycles, so you add a <code>bicycle</code> table.</p>
<p>To keep track of the different versions of the schema, some libraries maintain an internal table with a single row with a version number. It then performs migrations as needed. In our example, version 1 would have only the <code>car</code> table and version 2, also the <code>bicycle</code> table. The migration from version 1 to version 2 add the <code>bicycle</code> table.</p>
<p>SQLite offers the <a href="https://sqlite.org/pragma.html#pragma_user_version">user_version</a> pragma, to keep track of these versions. It is an integer <a href="https://sqlite.org/fileformat2.html#user_version_number">at a fixed offset</a> in the database file. It is simpler and more efficient than maintaining a table with versions, in particular because the table has to be found in the database file while the integer is available right away.</p>
<p>The drawback is that this is not portable between database engines. If you only use SQLite though, it is almost always the best option. To use it, you can write some code to check the value of the <code>user_version</code> pragma value right after opening the database. If it is lower than expected, then atomically 1) run the necessary migrations and 2) increment the <code>user_version</code> pragma value. I wrote a library to ease this task in <a href="https://cj.rs/rusqlite_migration/">rust</a> and here is an <a href="https://levlaz.org/sqlite-db-migrations-with-pragma-user_version/">example in python</a>.</p>
<h3 id="foreign_keys-on">foreign_keys on</h3>
<p>This may come as a surprise for folks with experience with other database system, but SQLite does not enforce foreign key constraints by default. Consequently, a foreign key can point to a row that does not exist.</p>
<p>This can be fixed with pragma <a href="https://sqlite.org/pragma.html#pragma_foreign_keys">foreign_keys</a>:</p>
<div class="highlight"><pre tabindex="0" style="color:#abb2bf;background-color:#282c34;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-sql" data-lang="sql"><span style="display:flex;"><span><span style="color:#e06c75">PRAGMA</span> <span style="color:#e06c75">foreign_keys</span> <span style="color:#56b6c2">=</span> <span style="color:#c678dd">ON</span>;
</span></span></code></pre></div><p>This comes at a performance cost however, because more checks are performed when inserting values with foreign keys. The cost is usually negligible but your mileage may vary.</p>
<p><em>Note</em>: pragma <a href="https://sqlite.org/pragma.html#pragma_foreign_key_check">foreign_key_check</a> can be used to check a particular table for violated foreign key constraint. This can be useful before enabling <code>foreign_keys</code> on a database with existing data.</p>
<h3 id="strict">STRICT</h3>




  
  
  
  

  <div class="alert alert-important">
    <p class="alert-heading">
      🟣
      
        Important
      
    </p>
    <p>Requires SQLite version <a href="https://www.sqlite.org/releaselog/3_37_1.html">3.37.0 (2021-11-27)</a></p>
  </div>



<p>Another peculiarity of SQLite is dynamic typing through <a href="https://www.sqlite.org/datatype3.html">type affinity</a>. When you define a column of type <code>INTEGER</code> in most other database engines, a value of type <code>TEXT</code> inserted in that column will be converted or return an error. But with SQLite, type <a href="https://www.sqlite.org/datatype3.html#type_affinity">affinities</a> mean that if the value is not of the expected type, it will be converted if possible or stored with a different type if necessary. That’s handy when prototyping, but you may want a stricter behavior for consistency with other major SQL databases or when working with strictly typed languages.</p>
<p>To this end, SQLite supports the <a href="https://www.sqlite.org/stricttables.html">STRICT</a> keyword at table creation. For instance, if a table <code>t</code> is created like so:</p>
<pre tabindex="0"><code>CREATE TABLE t(a INTEGER) STRICT;
</code></pre><p>then</p>
<pre tabindex="0"><code>INSERT INTO t VALUES(1);
INSERT INTO t VALUES(&#39;1&#39;);
</code></pre><p>are successful but</p>
<pre tabindex="0"><code>INSERT INTO t VALUES(&#39;f&#39;);
</code></pre><p>is not and returns <code>Runtime error: cannot store TEXT value in INTEGER column t.a (19)</code>.</p>
<p><strong>Note</strong>: Without the <code>STRICT</code> keyword at table creation, this last <code>INSERT INTO …</code> statement would have been successful and would have just returned a string.</p>
<h2 id="go-deeper">Go deeper</h2>
<p>The full list of supported pragma with detailed descriptions is available in the <a href="https://sqlite.org/pragma.html">documentation</a>. Some <a href="https://kerkour.com/sqlite-for-servers">more options</a> can be tweaked for the specific scenario of running a SQLite for a server.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>A significant improvement to WAL mode is <a href="https://www.sqlite.org/cgi/src/doc/wal2/doc/wal2.md">being developped</a>, where the WAL file won’t grow to unbound sizes.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item></channel></rss>