<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <link
    href="https://matklad.github.io/feed.xml"
    rel="self"
    type="application/atom+xml"
  />
  <link href="https://matklad.github.io" rel="alternate" type="text/html" />
  <updated>2026-05-30T01:09:22.129Z</updated>
  <id>https://matklad.github.io/feed.xml</id>
  <title type="html">matklad</title>
  <subtitle>matklad&#039;s Arts&amp;Crafts</subtitle>
  <author><name>Alex Kladov</name></author>
  <entry>
    <title type="text">TIL: Symlinking NixOS Dotfiles</title>
    <link
      href="https://matklad.github.io/2026/05/21/symlinking-nixos-dotfiles.html"
      rel="alternate"
      type="text/html"
      title="TIL: Symlinking NixOS Dotfiles"
    />
    <published>2026-05-21T00:00:00+00:00</published>
    <updated>2026-05-21T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/21/symlinking-nixos-dotfiles</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[The standard answer to managing dotfiles on NixOS is home-manager. I've never used it, due to two aesthetic and one practical objection:]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/21/symlinking-nixos-dotfiles.html"
    >
      <![CDATA[
<header>
  <h1>TIL: Symlinking NixOS Dotfiles</h1>
  <time class="meta" datetime="2026-05-21">May 21, 2026</time>
</header>
<p>The standard answer to managing dotfiles on NixOS is
<a href="https://github.com/nix-community/home-manager">home-manager</a>. I’ve never used it, due to two
aesthetic and one practical objection:</p>
<ul>
<li>
I avoid dependencies, especially in nix, which rivals Python in the number of approaches to
dependency management.
</li>
<li>
home-manager installs packages for the current user only, which makes sense on non-NixOS systems.
But on a single-user desktop system, I prefer having just one set of packages.
</li>
<li>
Having a source of truth for dotfiles be in nix store requires rebuilding your system to change
config, which gets in the way of Emacs-style direct tinkering.
</li>
</ul>
<p>The approach I like is storing dotfiles in the same repository as <code>flake.nix</code> / <code>configuration.nix</code>
and symlinking them in place.</p>
<p>The problem here is that NixOS seemingly doesn’t have a “native” way to say that <code>/a/b/c</code> should be
a symlink to <code>/c/d/e</code>. Or has it?</p>
<p>If you <a href="https://search.nixos.org/options?query=symlink">search options</a> for <code>symlink</code>, you’ll learn
about <a href="https://search.nixos.org/options?query=environment.etc#show=option%253Aenvironment.etc"><code>environment.etc</code></a> which allows you
to configure symlinks, but only for things in <code>/etc</code>, not your <code>~/.config</code>.</p>
<p>For the latter, you can use <a href="https://www.gnu.org/software/stow/">gnu stow</a> or some other dotfile
link manager, but the complexity of the problem of <em>just</em> managing symlinks doesn’t warrant yet
another dependency. It’s fine to do
<a href="https://github.com/matklad/config/blob/346afb44f0cc50a04b8e7008ab389a90a1dfdd0f/xtool/src/autostart.rs#L75-L105">this manually</a>.</p>
<p>But wouldn’t it be nice if this framework for declarative configuration of your system allowed you to
declaratively configure symlinks? Turns out this is possible, in roundabout way. Inaptly-named
<a href="https://www.man7.org/linux/man-pages/man8/systemd-tmpfiles.8.html">systemd-tmpfiles</a> allows
creating symlinks from a declarative config, and you can use NixOS to
<a href="https://search.nixos.org/options?channel=25.11&amp;query=systemd.tmpfiles.rules">configure</a>
<code>systemd-tmpfiles</code> itself (thanks, <a href="https://discourse.nixos.org/t/managing-config-files-why-not-use-mkoutofstoresymlink-for-everything/77643/21">NobbZ</a>!).</p>
<p>For example, if I want to symlink <code>~/dotfiles/git/config</code> to <code>.config/git/config</code>:</p>

<figure class="code-block">


<pre><code><span class="line">{</span>
<span class="line">  systemd.tmpfiles.<span class="hl-attr">rules</span> = [</span>
<span class="line">    <span class="hl-string">&quot;L+ /home/matklad/.config/git/config - - - - /home/matklad/dotfiles/git/config&quot;</span></span>
<span class="line">  ];</span>
<span class="line">}</span></code></pre>

</figure>
<p>No opinion at this point how this compares to a bespoke script or
<a href="https://github.com/feel-co/smfh">something more purpose-built</a>.</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Always Be Blaming</title>
    <link
      href="https://matklad.github.io/2026/05/18/always-be-blaming.html"
      rel="alternate"
      type="text/html"
      title="Always Be Blaming"
    />
    <published>2026-05-18T00:00:00+00:00</published>
    <updated>2026-05-18T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/18/always-be-blaming</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[A few tips on 4D-ing your code comprehension skills.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/18/always-be-blaming.html"
    >
      <![CDATA[
<header>
  <h1>Always Be Blaming</h1>
  <time class="meta" datetime="2026-05-18">May 18, 2026</time>
</header>
<p>A few tips on 4D-ing your code comprehension skills.</p>
<p>I wrote on the importance of reading code before:
<a href="https://matklad.github.io/2025/09/04/look-for-bugs.html" class="display"><em>Look Out For Bugs</em></a>
My default approach to reading is “predictive”: I don’t actually read the code line by line. Rather,
I try to understand the problem that it wants to solve, then imagine my own solution, and read the
“diff” between what I have in my mind and what I see in the editor. Non-empty “diff” signifies
either a bug in my understanding, or an opportunity to improve the code.</p>
<p>This is 2D reading, understanding a snapshot of code, frozen in time. This is usually enough to spot
“this feels odd” anomalies, worthy of further investigation.</p>
<p>Ideal code is memoryless — it precisely solves the problem at hand. Most real code is Markov —
the shape of the code at time <code>T</code> depends not only on the problem statement, but also on the shape
of the code at time <code>T - 1</code>. The 3D step is to trace the evolution of code over time,
<a href="https://en.wikipedia.org/wiki/Where_Do_We_Come_From%3F_What_Are_We%3F_Where_Are_We_Going%3F#/media/File:Paul_Gauguin_-_D'ou_venons-nous.jpg"><em>Where Do We Come From? What Are We? Where Are We Going?</em></a>.</p>
<p>The step after that is to understand the <em>why</em>. What were we thinking back then, when we wrote this
code? It’s useful to have the “theory of mind” concept ready here. I personally learned the term way
too late in my life, so let me give a short intro for <a href="https://xkcd.com/1053/">today’s lucky 10 000</a>.
Theory of mind is the ability to imagine yourself in someone else’s skin. Not just in their shoes
(“I certainly would have acted differently in that situation”), but with their mind (“<em>I</em> wouldn’t
have acted that way, but I get why <em>they</em> did”). This is something people learn. The experimental
setup here is to have a child in a room with toys, with a doll sitting near the opposite end of the
room, and asking the child “what does the doll see?”. Younger children describe the room from <em>their</em>
perspective, older begin to intuit that doll’s perspective is different.</p>
<p>So <em>this</em> is the goal of reading code — understanding <em>what</em> the original author was thinking, and
<em>why</em>.</p>
<hr>
<p>End of the mumbo-jumbo, some practical advice. First, read
<span class="display"><a href="https://mislav.net/2014/02/hidden-documentation/"><em>Every line of code is always documented</em></a>,</span>
it is very good.</p>
<p>Second, make sure it is <em>effortless</em> for you to find out how a given snippet of code evolved. This
is harder than it seems! Just <code>git blame</code> isn’t an answer — mind the gap between the problem
that’s easy to solve, and the problem in need of solving.</p>
<p><code>git blame</code> answers spatial question of “how each line appeared in this file”, because there’s a
relatively straightforward UI for this — annotate each line with a commit hash. But this is not
the question you are asking most of the time! You don’t care about the file! There’s a small snippet
of code in the middle, and you want a temporal history of <em>that</em>.</p>
<p>As much as I
<a href="https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/">don’t like working in the browser</a>
GitHub’s web interface for blaming is probably better than what you get locally by default. It
starts with the <kbd><kbd>y</kbd></kbd> shortcut, which resolves a symbolic reference like</p>

<figure class="code-block">


<pre><code><span class="line">https://github.com/tigerbeetle/tigerbeetle/blob/main/src/vsr/replica.zig</span></code></pre>

</figure>
<p>into the one which has a commit hash in the URL:</p>

<figure class="code-block">


<pre><code><span class="line">https://github.com/tigerbeetle/tigerbeetle/blob/c54f613a2eb2a127a0ba212704e3fa988c42e5cb/src/vsr/replica.zig</span></code></pre>

</figure>
<p>This commit hash is critical, because it anchors the entire repository — if you open a different
file from the web UI, it will be shown as of that commit. This enables you to not myopically focus
on just the diff in question, but to absorb the entire context at that point in time.</p>
<p>So my usual web workflow is:</p>
<ul>
<li>
<kbd><kbd>ctrl</kbd>+<kbd>f</kbd></kbd> to find the line I am interested in
</li>
<li>
<kbd><kbd>b</kbd></kbd> to toggle blame
</li>
<li>
Click “blame prior to change” a couple of times, repeating <kbd><kbd>ctrl</kbd>+<kbd>f</kbd></kbd> to go back to the snippet
I am curious about.
</li>
<li>
<kbd><kbd>cmd</kbd></kbd>-click on the commits that are potentially relevant, pinning their commit hashes
in the URL in new tabs.
</li>
<li>
Then, from the commit page, “Browse files” button to then go and <kbd><kbd>t</kbd></kbd> to other files. Or,
<kbd><kbd>cmd</kbd>+<kbd>l</kbd></kbd> to focus browser’s address bar, and <code>s/commit/tree/</code> (or back!) as needed, to switch
between diff and snapshot views.
</li>
</ul>
<p>Again, my goal here is not to annotate a diff on a file but rather to get a “virtual checkout” as of
the interesting commit.</p>
<p>This web approach is what I was using throughout most of my career, but I’ve finally found a way to
replicate it locally. The idea is to make blaming “in-place”. Instead of <code>git blame</code> annotating
lines of code, I directly switch to a historical commit. I have the following
<a href="https://susam.github.io/devil/">devil</a> <a href="https://github.com/abo-abo/hydra">hydra</a> of shortcuts:</p>
<p><kbd><kbd>, b l</kbd></kbd> blames line. It notes the <code>$line</code> the cursor is at, runs
<span class="display"><code>git blame -L $line,$line</code></span>
to find <code>$commit</code> that introduced the line, and then runs
<span class="display"><code>git switch --detach $commit</code></span>
to check it out. I have
<a href="https://matklad.github.io/2024/07/25/git-worktrees.html">a dedicated worktree</a> for code archeology,
so I don’t worry about trashing my work. There’s also a half-hearted attempt to maintain “logical”
cursor position, but it doesn’t work very well. Is there some git command that tells me directly
“what’s the equivalent of <code>$file:$line:column</code> in <code>$sha-A</code> for <code>$sha-B</code>?”</p>
<p><kbd><kbd>, b p</kbd></kbd> blames parent. Which is just switching to the parent commit of the current <code>HEAD</code>, what
“blame before this change” does on GitHub (it works slightly differently because it assumes that
<kbd><kbd>, b l</kbd></kbd> was the previous command)</p>
<p><kbd><kbd>, b u</kbd></kbd> undoes the last blaming operation, switching to the previous point. I <em>really</em> love that, on
the web, I can <kbd><kbd>cmd</kbd></kbd>-click to create an alternative branch of exploration. In theory, this is
replicatable locally, but I prefer to destructively mutate a single working tree on disk. A big
reason for preferring in-place blame is that LSP, <code>./zig/zig build test</code>, <code>rg</code> and the like just
work. That’s more important for me than the garden of forking paths, and undo is an acceptable
work-around.</p>
<p>Finally, <kbd><kbd>, b w</kbd></kbd> copies GitHub link to the current commit and line, which I can paste into the
browser. An <em>enormous</em> problem with modern version control landscape is that absolutely critical
information in the form of code review comments is not a part of the git repository, and is locked
in someone else’s proprietary database. I <a href="https://tigerbeetle.com/blog/2025-08-04-code-review-can-be-better/">failed to
solve</a> this problem in one
weekend, and had to begrudgingly adapt. Opening the commit in a browser links you to the PR and its
discussion as well.</p>
<p>Implementing this blame workflow required
<a href="https://github.com/matklad/config/blob/801d0781b005db574e6b42b813058741dd8ef390/tools/my-code/src/blame.ts">a bit of custom code</a>.
Feel free to use it, but beware that it’s somewhat crufty, especially around maintaining current
cursor position. Making a production-ready version of this sounds like a fun project ;-)</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Catch Flakes On Main</title>
    <link
      href="https://matklad.github.io/2026/05/14/catch-flakes-on-main.html"
      rel="alternate"
      type="text/html"
      title="Catch Flakes On Main"
    />
    <published>2026-05-14T00:00:00+00:00</published>
    <updated>2026-05-14T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/14/catch-flakes-on-main</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html"><![CDATA[A small Mechanical Habit today:]]></summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/14/catch-flakes-on-main.html"
    >
      <![CDATA[
<header>
  <h1>Catch Flakes On Main</h1>
  <time class="meta" datetime="2026-05-14">May 14, 2026</time>
</header>
<p>A small <a href="https://matklad.github.io/2025/12/06/mechanical-habits.html"><em>Mechanical Habit</em></a> today:</p>

<figure class="blockquote">
<blockquote><p>When using not rocket science rule / merge queue, continue to redundantly run the full test suite
on main. Maintain an easily accessible list of recent main failures — these are the flaky tests
to eradicate.</p>
</blockquote>

</figure>
<p>For an example, see the “Flakes” link on
<a href="https://devhub.tigerbeetle.com" class="display url">https://devhub.tigerbeetle.com</a></p>

<figure>

<img alt="" src="https://github.com/user-attachments/assets/09dffa9e-cdae-48e2-a4ed-80278da53bb2" width="2310" height="1746">
</figure>
<p>Flaky tests are tests that fail intermittently, once in a thousand runs. This might be due to a
genuine bug (assumptions about scheduling that <em>mostly</em> hold) or due to instability of underlying
infrastructure (e.g., inability to download a release from GitHub, or to delete a folder on Windows).
In either case, flaky tests are a huge productivity drain — as the size and complexity of test
suite grows, more and more CI runs fail spuriously, even as each individual test almost always
passes.</p>
<p>Flaky tests are challenging to deal with — if you are working on landing a PR and your CI fails
due to an obvious flake, the temptation to just re-run the test suite is enormous, especially if
there’s a certain background dissatisfaction with infrastructure stability.</p>
<p>If you are of a mind to do some flake squashing, then your PRs will be green just to spite you! And
working off of others’ PRs would require first to separate flakes from genuine failures.</p>
<p>This is why the merge queue is powerful: if there’s a guarantee that every commit on the main branch
passes the tests, then every failure on main is a flake, by definition. Collecting all such
failures into a single list compresses time, allows to prioritize the most impactful sources of
instability, and reveals correlations between failures.</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Learning Software Architecture</title>
    <link
      href="https://matklad.github.io/2026/05/12/software-architecture.html"
      rel="alternate"
      type="text/html"
      title="Learning Software Architecture"
    />
    <published>2026-05-12T00:00:00+00:00</published>
    <updated>2026-05-12T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/12/software-architecture</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[In reply to an email asking about learning software design skills as a researcher physicist:]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/12/software-architecture.html"
    >
      <![CDATA[
<header>
  <h1>Learning Software Architecture</h1>
  <time class="meta" datetime="2026-05-12">May 12, 2026</time>
</header>
<p>In reply to an email asking about learning software design skills as a researcher physicist:</p>
<p>I was attached to <a href="https://lp.jetbrains.com/biolabs/">a bioinformatics lab</a> early in my career,
so I think I understand what you are talking about, the phenomenon of “scientific code”! My
thoughts:</p>
<p><strong>First</strong> meta observation is that “software design” is something best learned by doing. While I had
some formal “design” courses at the University, and I was even “an architect” for our course
project, that stuff was mostly make-believe, kindergarteners playing fire-fighters. What really
taught me how to do stuff was an accident of my career, where my second real project
(<a href="https://github.com/intellij-rust/intellij-rust">IntelliJ Rust</a>)
propelled me to a position of software leadership, and made design my problem. I did make a few
mistakes in IJ Rust, but nothing too horrible, and I learned a lot. So that’s good news — software
engineering is simple enough that an inquisitive mind can figure it out from first principles (and
reading random blog posts).</p>
<p><strong>Second</strong> meta observation, the bad news:
<a href="https://en.wikipedia.org/wiki/Conway%27s_law">Conway’s law</a>
is important. Softwaregenesis repeats the social architecture of the organization producing
software. Or, as put eloquently by <a href="https://neugierig.org/software/blog/2020/05/ninja.html">neugierig</a>,</p>

<figure class="blockquote">
<blockquote><p>If I were to summarize what I learned in a single sentence, it would be this: we talk about
programming like it is about writing code, but the code ends up being less important than the
architecture, and the architecture ends up being less important than social issues.</p>
</blockquote>

</figure>
<p>I suspect that the difference you perceive between industrial and scientific software is not so much
about software-building knowledge, but rather about the field of incentives that compels people to
produce the software. Something like “my PhD needs to publish a paper in three months” is perhaps a
significant explainer?</p>
<p>Two things you can do here. <em>One</em>, at times you get a chance to design or nudge an incentive structure
for a project. This happens once in a blue moon, but is very impactful. <em>This</em> is the secret sauce
behind <a href="https://github.com/tigerbeetle/tigerbeetle/blob/0.17.4/docs/TIGER_STYLE.md">TIGER_STYLE</a>,
not the set of rules per se, but the social context that makes this set of rules a good idea.</p>
<p><em>Two</em>, you can speedrun the four stages of grief to acceptance. Incentive structure is almost
never what you want it to be, but, if you can’t change it, you can adapt to it. This is also true
about most industrial software projects — there’s never a time to do a thing properly, you must do
the best you can, given constraints.</p>
<p>Let me use <a href="https://github.com/rust-lang/rust-analyzer">rust-analyzer</a> as an example. The physical
reality of the project is that it’s simultaneously very deep (it’s a compiler! Yay!) and very wide
(opposite to an LLM, a classical IDE is <em>a lot</em> of purpose-built special features). The social
reality is that “deep compiler” can attract a few brilliant dedicated contributors, and that the
“breadth features” can be a good fit for an army of weekend warriors, people who learn Rust, who
don’t have sustained capacity to participate in the project, but who can sink an hour or two to
scratch their own itch.</p>
<p>My insistence that <code>rust-analyzer</code> doesn’t require building <code>rustc</code>, that it builds on stable, that
it doesn’t have any C dependencies, and that the entire test suite takes seconds, was in the
service of the goal of attracting high-impact contributors. I was wrangling the build system to make
sure people can work on the borrow checker without thinking about anything else.</p>
<p>To attract weekend warriors, the internals of rust-analyzer are split into multiple independent
features, where each feature is guarded by <code>catch_unwind</code> at runtime. The thinking was that I
<em>explicitly</em> don’t want to care too much about quality there, that the bar for getting a feature PR
in is “happy path works &amp; tested”. It’s fine if the code crashes, it will only attract
further contributors, provided that:</p>
<ul>
<li>
the quality is isolated to a feature, and doesn’t spill over,
</li>
<li>
at runtime, the crash is invisible to the user (it’s crucial that rust-analyzer features
work with an immutable snapshot, and can’t poison the data).
</li>
</ul>
<p>In contrast, when working on the core <em>spine</em> which provided support for features, I was very
relatively more pedantic about quality.</p>
<p>A word of caution about adapting to, rather than fixing incentive structure — the future is
uncertain, and tends to happen in the least convenient manner. The original motivation behind
rust-analyzer <em>experiment</em> was to avoid the need to write a parallel compiler (the one in IntelliJ
Rust), and to <em>prototype</em> a better architecture for LSP, so that the learnings could be backported
to <code>rustc</code>. So, even in core (especially in core), the code was <em>very</em> experimental. Oh well. Stuck
with one more compiler now, I guess?</p>
<p>I might hazard a guess that something similar happened to uutils project, which started as the
primary destination for people learning Rust, and ended up as Ubuntu coreutils implementation.</p>
<p><strong>Third</strong>, now to some concrete recommendations. Sadly, I don’t know of a single book I can recommend
which contains the truths. I suspect one can only find such a book in an apocryphal short story by
Borges: practice seems to be an essential element here. But here are some things worth paying
attention to:</p>
<p><a href="https://www.destroyallsoftware.com/talks/boundaries">Boundaries</a> talk by Gary Bernhardt is all-time
favorite. It contains solid object-level advice, and, for me, it triggered the meta inquiry.</p>
<p><a href="https://matklad.github.io/2021/05/31/how-to-test.html">How to Test</a> is something I wish I had. I
immediately understood the importance of testing, but it took me a long time to grow arrogant enough
to admit that most widely-cited testing advice is shamanistic snake-oil, and to conceptualize what
actually works.</p>
<p><a href="https://zguide.zeromq.org/docs/chapter6/">∅MQ guide</a> and, more generally,
<a href="http://hintjens.com">writings by Pieter Hintjens</a>
introduced me to Conway’s Law thinking.
That “feature development” architecture of rust-analyzer? –
<a href="http://hintjens.com/blog:106">optimistic merging</a>, applied.</p>
<p><a href="https://www.scattered-thoughts.net/writing/reflections-on-a-decade-of-coding/">Reflections on a decade of coding</a>
by Jamii is excellent, goes very meta. It is intentionally the first of <a href="https://matklad.github.io/links.html">my links</a>.</p>
<p><a href="https://www.tedinski.com/archive/">Ted Kaminski</a> blog is the closest there is to a coherent
theory of software development, appropriately framed as a set of notes to a non-existing book!</p>
<p>As for the actual books, <a href="https://abseil.io/resources/swe-book"><em>Software Engineering at Google</em></a>
and Ousterhout’s <em>The Philosophy of Software Design</em> are often recommended. They are good. SWE, in
particular, helped me with
<a href="https://matklad.github.io/2022/07/04/unit-and-integration-tests.html">a couple of important names</a>.
But they weren’t ground breaking for me.</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Steering Zig Fmt</title>
    <link
      href="https://matklad.github.io/2026/05/08/steering-zig-fmt.html"
      rel="alternate"
      type="text/html"
      title="Steering Zig Fmt"
    />
    <published>2026-05-08T00:00:00+00:00</published>
    <updated>2026-05-08T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/08/steering-zig-fmt</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[Two tips on using zig fmt effectively. Read this if you are writing Zig, or if you are implementing a code formatter.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/08/steering-zig-fmt.html"
    >
      <![CDATA[
<header>
  <h1>Steering Zig Fmt</h1>
  <time class="meta" datetime="2026-05-08">May 8, 2026</time>
</header>
<p>Two tips on using <code>zig fmt</code> effectively. Read this if you are writing Zig, or if you are
implementing a code formatter.</p>
<p>For me, <code>zig fmt</code> is better than any other formatter I used: <code>rustfmt</code>, the one in IntelliJ,
<code>deno fmt</code>. <code>zig fmt</code> is steerable. For every syntactic construct, it has several variations for
how it might be laid out. The variation used is selected by looking at what’s currently in a file.</p>
<p>Easier to show a pair of examples:</p>

<figure class="code-block">


<pre><code><span class="line">    f(<span class="hl-numbers">1</span>, <span class="hl-numbers">2</span>,</span>
<span class="line">      <span class="hl-numbers">3</span>);</span>
<span class="line"></span>
<span class="line"><span class="hl-comment">// -&gt; zig fmt -&gt;</span></span>
<span class="line"></span>
<span class="line">    f(<span class="hl-numbers">1</span>, <span class="hl-numbers">2</span>, <span class="hl-numbers">3</span>);</span></code></pre>

</figure>

<figure class="code-block">


<pre><code><span class="line">    f(<span class="hl-numbers">1</span>, <span class="hl-numbers">2</span>,</span>
<span class="line">      <span class="hl-numbers">3</span>,);</span>
<span class="line"></span>
<span class="line"><span class="hl-comment">// -&gt; zig fmt -&gt;</span></span>
<span class="line"></span>
<span class="line">    f(</span>
<span class="line">        <span class="hl-numbers">1</span>,</span>
<span class="line">        <span class="hl-numbers">2</span>,</span>
<span class="line">        <span class="hl-numbers">3</span>,</span>
<span class="line">    );</span></code></pre>

</figure>
<p>Depending on the trailing comma, function call is formatted on a single line, or with one argument
per line.</p>
<p>The way this plays out in practice is that you <em>decide</em> how you want to lay out the code, add a
couple of <code>,</code>, hit the reformat shortcut
(<a href="https://matklad.github.io/2024/10/08/two-tips.html#s-1"><kbd><kbd>, p</kbd></kbd> is mine</a>),
and <code>zig fmt</code> does the rest. For me, this works better than the alternative of the formatter
guessing. 90% of great formatting are blank lines between logical blocks and tasteful choice of
intermediate variables, so you might as well lean into key choices, rather than eliminate them.</p>
<p>I know of one non-trivial formatting customization point: columnar layout for arrays:</p>

<figure class="code-block">


<pre><code><span class="line">    .{ <span class="hl-numbers">1</span>, <span class="hl-numbers">2</span>, <span class="hl-numbers">3</span>,</span>
<span class="line">       <span class="hl-numbers">4</span>, <span class="hl-numbers">5</span>, <span class="hl-numbers">6</span>, <span class="hl-numbers">7</span>, <span class="hl-numbers">8</span>, <span class="hl-numbers">9</span>, <span class="hl-numbers">10</span>, <span class="hl-numbers">11</span>,  };</span></code></pre>

</figure>
<p>One would think that trailing comma would lead to a number-per-line layout, but, for arrays, <code>zig
fmt</code> also takes note of the first line break. In this case, the line break comes after the first
three items, so we get three numbers per line, aligned:</p>

<figure class="code-block">


<pre><code><span class="line">    .{</span>
<span class="line">        <span class="hl-numbers">1</span>,  <span class="hl-numbers">2</span>,  <span class="hl-numbers">3</span>,</span>
<span class="line">        <span class="hl-numbers">4</span>,  <span class="hl-numbers">5</span>,  <span class="hl-numbers">6</span>,</span>
<span class="line">        <span class="hl-numbers">7</span>,  <span class="hl-numbers">8</span>,  <span class="hl-numbers">9</span>,</span>
<span class="line">        <span class="hl-numbers">10</span>, <span class="hl-numbers">11</span>,</span>
<span class="line">    };</span></code></pre>

</figure>
<p>How cool is that!</p>
<p>Furthermore, with judicious use of <code>++</code>
(<a href="https://ziglang.org/documentation/0.16.0/#:~:text=a%20++%20b">array concatenation</a>),
you can vary the number of items per line. When I need to pass <code>--key</code> <code>value</code> pairs to subprocess,
I often go for formatting like this:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">try</span> run(<span class="hl-operator">&amp;</span>(.{ <span class="hl-string">&quot;aws&quot;</span>, <span class="hl-string">&quot;s3&quot;</span>, <span class="hl-string">&quot;sync&quot;</span>, path, url } <span class="hl-operator">+</span><span class="hl-operator">+</span> .{</span>
<span class="line">    <span class="hl-string">&quot;--include&quot;</span>,            <span class="hl-string">&quot;*.html&quot;</span>,</span>
<span class="line">    <span class="hl-string">&quot;--include&quot;</span>,            <span class="hl-string">&quot;*.xml&quot;</span>,</span>
<span class="line">    <span class="hl-string">&quot;--metadata-directive&quot;</span>, <span class="hl-string">&quot;REPLACE&quot;</span>,</span>
<span class="line">    <span class="hl-string">&quot;--cache-control&quot;</span>,      <span class="hl-string">&quot;max-age=0&quot;</span>,</span>
<span class="line">}));</span></code></pre>

</figure>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Minimal Viable Zig Error Contexts</title>
    <link
      href="https://matklad.github.io/2026/05/03/zig-error-context.html"
      rel="alternate"
      type="text/html"
      title="Minimal Viable Zig Error Contexts"
    />
    <published>2026-05-03T00:00:00+00:00</published>
    <updated>2026-05-03T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/05/03/zig-error-context</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[Out of the box, Zig provides minimal and sufficient facilities for error handling --- strongly-typed error codes. Error reporting is left to the user. Idiomatic solution is to pass a Diagnostics out parameter (sink) to materialize human-readable strings as needed.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/05/03/zig-error-context.html"
    >
      <![CDATA[
<header>
  <h1>Minimal Viable Zig Error Contexts</h1>
  <time class="meta" datetime="2026-05-03">May 3, 2026</time>
</header>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> process_file</span>(io: Io, path: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span>) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">errdefer</span> log.err(<span class="hl-string">&quot;path={s}&quot;</span>, .{path});</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> fd = <span class="hl-keyword">try</span> Io.Dir.cwd().openFile(io, path, .{});</span>
<span class="line">    <span class="hl-keyword">defer</span> fd.close(io);</span>
<span class="line"></span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line">}</span></code></pre>

</figure>
<p>Out of the box, Zig provides minimal and sufficient facilities for error <em>handling</em> —
<a href="https://matklad.github.io/2025/11/06/error-codes-for-control-flow.html">strongly-typed error codes</a>.
Error <em>reporting</em> is left to the user. Idiomatic solution is to pass a <code>Diagnostics</code> out parameter
(“sink”) to materialize human-readable strings as needed.</p>
<p>Diagnostics pattern works well for “production” code, but for more script-y code it adds too much
friction relative to the default option of a plain
<span class="display"><code>try fallible()</code>,</span>
which of course gives a less than ideal message on failure:</p>

<figure class="code-block">


<pre><code><span class="line">λ zig build</span>
<span class="line">error: FileNotFound</span>
<span class="line">~/.cache/zig/p/../lib/std/Io/Threaded.zig:4866:35: 0x1044126c7 in dirOpenFilePosix (fail)</span>
<span class="line">                        .NOENT =&gt; return error.FileNotFound,</span>
<span class="line">                                  ^</span>
<span class="line">~/.cache/zig/p/../lib/std/Io/Dir.zig:578:5: 0x104347d8b in openFile (fail)</span>
<span class="line">    return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);</span>
<span class="line">    ^</span>
<span class="line">~/fail/main.zig:10:16: 0x10443da5f in f (fail)</span>
<span class="line">    const fd = try Io.Dir.cwd().openFile(io, path, .{});</span>
<span class="line">               ^</span>
<span class="line">~/fail/main.zig:6:5: 0x10443db47 in main (fail)</span>
<span class="line">    try process_file(io, &quot;data.txt&quot;);</span>
<span class="line">    ^</span></code></pre>

</figure>
<p>Error trace is helpful, but knowing <em>which</em> file is the problem is even more so.</p>
<p>The first attempt at finding a middle ground between fully-fledged diagnostics sink pattern and a
plain try is something like this:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> fd = dir.openFile(io, path, .{}) <span class="hl-keyword">catch</span> <span class="hl-operator">|</span>err<span class="hl-operator">|</span> {</span>
<span class="line">    log.err(<span class="hl-string">&quot;failed to open file &#x27;{s}&#x27;: {t}&quot;</span>, .{path, err});</span>
<span class="line">    <span class="hl-keyword">return</span> err;</span>
<span class="line">}</span></code></pre>

</figure>
<p>Unsatisfactory. The friction is high, you need to come up with a reasonably-sounding error message,
the “happy path” of the code is obscured, and you need to repeat this for every fallible operation.</p>
<p>A worse-is-better version of the above code is</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">errdefer</span> log.err(<span class="hl-string">&quot;path={s}&quot;</span>, .{path});</span>
<span class="line"><span class="hl-keyword">const</span> fd = <span class="hl-keyword">try</span> dir.openFile(io, path, .{});</span></code></pre>

</figure>
<p>That is, just log error context as <code>key=value</code> pairs, guarded by <code>errdefer</code>. The result is not
pretty, but passable:</p>

<figure class="code-block">


<pre><code><span class="line">λ zig build</span>
<span class="line">error: path=./data.txt</span>
<span class="line">error: FileNotFound</span>
<span class="line">~/.cache/zig/p/../lib/std/Io/Threaded.zig:4866:35: 0x1044126c7 in dirOpenFilePosix (fail)</span>
<span class="line">                        .NOENT =&gt; return error.FileNotFound,</span>
<span class="line">                                  ^</span>
<span class="line">~/.cache/zig/p/../lib/std/Io/Dir.zig:578:5: 0x104347d8b in openFile (fail)</span>
<span class="line">    return io.vtable.dirOpenFile(io.userdata, dir, sub_path, options);</span>
<span class="line">    ^</span>
<span class="line">~/fail/main.zig:10:16: 0x10443da5f in f (fail)</span>
<span class="line">    const fd = try Io.Dir.cwd().openFile(io, path, .{});</span>
<span class="line">               ^</span>
<span class="line">~/fail/main.zig:6:5: 0x10443db47 in main (fail)</span>
<span class="line">    try process_file(io, &quot;data.txt&quot;);</span>
<span class="line">    ^</span></code></pre>

</figure>
<p>The friction is reduced a lot:</p>
<ul>
<li>
No need to come up with any error messages beyond existing variable names.
</li>
<li>
No need to change any of the <code>try</code>s.
</li>
<li>
The context is set per-block. If a function does several fallible operations on a file, the
path needs to be specified only once.
</li>
<li>
The context is “telescopic” every function in the call-stack can add its own context.
</li>
</ul>
<p>There’s one huge drawback though — the error message is logged, even if the error is subsequently
handled. This is especially important in Zig 0.16, where cancelation
(<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1677r2.pdf">serendipitous-success</a>)
is a possible error for any IO-ing operation, and which is intended to be handled, rather than
reported.</p>
<hr>
<p>Generalizing:</p>
<ul>
<li>
Happy path adds context to all operations in-progress.
</li>
<li>
Errors materialize current context.
</li>
</ul>
<p>This does feel like a better error management strategy than decorating errors individually, when
they happen. I wonder which language features facilitate this style?</p>
<p>This article
<a href="https://goldstein.lol/posts/error-progress/" class="display url">https://goldstein.lol/posts/error-progress/</a>
rather convincingly argues that the answer might be “none”?</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">256 Lines or Less: Test Case Minimization</title>
    <link
      href="https://matklad.github.io/2026/04/20/test-case-minimization.html"
      rel="alternate"
      type="text/html"
      title="256 Lines or Less: Test Case Minimization"
    />
    <published>2026-04-20T00:00:00+00:00</published>
    <updated>2026-04-20T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/04/20/test-case-minimization</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[Property Based Testing and fuzzing are a deep and science-intensive topic. There are enough advanced techniques there for a couple of PhDs, a PBT daemon, and a client-server architecture. But I have this weird parlor-trick PBT library, implementable in a couple of hundred lines of code in one sitting.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/04/20/test-case-minimization.html"
    >
      <![CDATA[
<header>
  <h1>256 Lines or Less: Test Case Minimization</h1>
  <time class="meta" datetime="2026-04-20">Apr 20, 2026</time>
</header>
<p>Property Based Testing and fuzzing are a deep and science-intensive topic. There are enough advanced
techniques there for a couple of PhDs, a PBT daemon, and
a <a href="https://antithesis.com/blog/2026/hegel/">client-server architecture</a>. But I have this weird
parlor-trick PBT library, implementable in a couple of hundred lines of code in one sitting.</p>
<p>This week I’ve been thinking about a cool variation of a consensus algorithm. I implemented it on the
weekend. And it took just a couple of hours to write a PBT library itself first, and then a test,
that showed a deep algorithmic flaw in my thinking (after a dozen trivial flaws in my coding).
So, I don’t get to write more about consensus yet, but I at least can write about the library. It is
very simple, simplistic even. To use an old Soviet joke about
<a href="https://en.wikipedia.org/wiki/Isaac_Babel">Babel</a> and <a href="https://en.wikipedia.org/wiki/August_Bebel">Bebel</a>,
it’s Gogol rather than Hegel. But for just 256 lines, it’s one of the highest power-to-weight ratio
tools in my toolbox.</p>
<p>Read this post if:</p>
<ul>
<li>
You want to stretch your generative testing muscles.
</li>
<li>
You are a do-it-yourself type, and wouldn’t want to pull a ginormous PBT library off the shelf.
</li>
<li>
You would pull a library, but want to have a more informed opinion about available options, about
essential and accidental complexity.
</li>
<li>
You want some self-contained real-world Zig examples :P
</li>
</ul>
<p>Zig works well here because it, too, is exceptional in its power-to-weight.</p>
<section id="FRNG">

<h2><a href="#FRNG">FRNG</a></h2>
<p>The implementation is a single file,
<a href="https://gist.github.com/matklad/343d13547c8bfe9af310e2ca2fbfe109"><code>FRNG.zig</code></a>,
because the core abstraction here is a Finite
Random Number Generator — a PRNG where all numbers are pre-generated, and can run out. We start
with standard boilerplate:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> std = <span class="hl-built_in">@import</span>(<span class="hl-string">&quot;std&quot;</span>);</span>
<span class="line"><span class="hl-keyword">const</span> assert = std.debug.assert;</span>
<span class="line"></span>
<span class="line">entropy: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span>,</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">const</span> Error = <span class="hl-keyword">error</span>{OutOfEntropy};</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">const</span> FRNG = <span class="hl-built_in">@This</span>();</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> init</span>(entropy: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span>) FRNG {</span>
<span class="line">    <span class="hl-keyword">return</span> .{ .entropy = entropy };</span>
<span class="line">}</span></code></pre>

</figure>
<p>In Zig, files are structs: you obviously need structs, and the language becomes simpler if structs
are re-used for what files are. In the above
<span class="display"><code>const FRNG = @This()</code></span>
assigns a conventional name to the file struct, and
<span class="display"><code>entropy: []const u8</code></span>
declares instance fields (only one here). <code>const Error</code> and <code>fn init</code> are “static” (container
level) declarations.</p>
<p>The only field we have is just a slice of raw bytes, our pre-generated random numbers. And the only
error condition we can raise is <code>OutOfEntropy</code>.</p>
<p>The simplest thing we can generate is a slice of bytes. Typically, API for this takes a mutable
slice as an out parameter:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> fill</span>(prng: <span class="hl-operator">*</span>PRNG, bytes: []<span class="hl-type">u8</span>) <span class="hl-type">void</span> { ... }</span></code></pre>

</figure>
<p>But, due to pre-generated nature of FRNG, we can return the slice directly, provided that we have
enough entropy. This is going to be our (sole) basis function, everything else is going to be a
convenience helper on top:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> bytes</span>(frng: <span class="hl-operator">*</span>FRNG, size: <span class="hl-type">usize</span>) Error<span class="hl-operator">!</span>[]<span class="hl-keyword">const</span> <span class="hl-type">u8</span> {</span>
<span class="line">    <span class="hl-keyword">if</span> (frng.entropy.len &lt; size) <span class="hl-keyword">return</span> <span class="hl-keyword">error</span>.OutOfEntropy;</span>
<span class="line">    <span class="hl-keyword">const</span> result = frng.entropy[<span class="hl-numbers">0</span>..size];</span>
<span class="line">    frng.entropy = frng.entropy[size..];</span>
<span class="line">    <span class="hl-keyword">return</span> result;</span>
<span class="line">}</span></code></pre>

</figure>
<p>The next simplest thing is an array (a slice with a fixed size):</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> array</span>(frng: <span class="hl-operator">*</span>FRNG, <span class="hl-keyword">comptime</span> size: <span class="hl-type">usize</span>) Error<span class="hl-operator">!</span>[size]<span class="hl-type">u8</span> {</span>
<span class="line">    <span class="hl-keyword">return</span> (<span class="hl-keyword">try</span> frng.bytes(size))[<span class="hl-numbers">0</span>..size].<span class="hl-operator">*</span>;</span>
<span class="line">}</span></code></pre>

</figure>
<p>Notice how Zig goes from runtime-known slice length, to comptime known array type. Because <code>size</code> is
a <code>comptime</code> constant, slicing <code>[]const u8</code> with <code>[0..size]</code> returns a pointer to array,
<span class="display"><code>*const [size]u8</code>.</span></p>
<p>We can re-interpret a 4-byte array into <code>u32</code>. But, because this is Zig, we can trivially generalize
the function to work for any integer type, by passing in <code>Int</code> comptime parameter of type <code>type</code>:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> builtin = <span class="hl-built_in">@import</span>(<span class="hl-string">&quot;builtin&quot;</span>);</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> int</span>(frng: <span class="hl-operator">*</span>FRNG, Int: <span class="hl-type">type</span>) Error<span class="hl-operator">!</span>Int {</span>
<span class="line">    <span class="hl-keyword">comptime</span> {</span>
<span class="line">        assert(<span class="hl-built_in">@typeInfo</span>(Int).int.signedness <span class="hl-operator">==</span> .unsigned);</span>
<span class="line">        assert(builtin.cpu.arch.endian() <span class="hl-operator">==</span> .little);</span>
<span class="line">    }</span>
<span class="line">    <span class="hl-keyword">return</span> <span class="hl-built_in">@bitCast</span>(<span class="hl-keyword">try</span> frng.array(<span class="hl-built_in">@sizeOf</span>(Int)));</span>
<span class="line">}</span></code></pre>

</figure>
<p>This function is monomorphised for every <code>Int</code> type, so <code>@sizeOf(Int)</code> becomes a compile-time
constant we can pass to <code>fn array</code>.</p>
<p>Production code would be endian-clean here, but, for simplicity, we encode our endianness assumption
as a compile-time assertion. Note how Zig communicates information about endianness to the program.
There isn’t any kind of side-channel or extra input to compilation, like <code>--cfg</code> flags. Instead, the
compiler materializes all information about target CPU as Zig code. There’s a <code>builtin.zig</code> file
somewhere in the compiler caches directory that contains</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">const</span> cpu: std.Target.Cpu = .{</span>
<span class="line">    .arch = .aarch64,</span>
<span class="line">    .model = <span class="hl-operator">&amp;</span>std.Target.aarch64.cpu.apple_m3,</span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line">}</span></code></pre>

</figure>
<p>This file can be accessed via <span class="display"><code>@import("builtin")</code></span> and all the constants inspected at
compile time.</p>
<p>We can make an integer, and a boolean is even easier:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> boolean</span>(frng: <span class="hl-operator">*</span>FRNG) Error<span class="hl-operator">!</span><span class="hl-type">bool</span> {</span>
<span class="line">    <span class="hl-keyword">return</span> (<span class="hl-keyword">try</span> frng.int(<span class="hl-type">u8</span>)) <span class="hl-operator">&amp;</span> <span class="hl-numbers">1</span> <span class="hl-operator">==</span> <span class="hl-numbers">1</span>;</span>
<span class="line">}</span></code></pre>

</figure>
<p>Strictly speaking, we only need one bit, not one byte, but tracking individual bits is too much of a
hassle.</p>
<p>From an arbitrary int, we can generate an int in range. As per
<a href="https://matklad.github.io/2025/03/31/random-numbers-included.html"><em>Random Numbers Included</em></a>,
we use a closed range, which makes the API infailable and is usually more convenient at the
call-site:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> int_inclusive</span>(frng: <span class="hl-operator">*</span>FRNG, Int: <span class="hl-type">type</span>, max: Int) Error<span class="hl-operator">!</span>Int</span></code></pre>

</figure>
<p>As a bit of PRNG trivia, while this could be implemented as
<span class="display"><code>frng.int(Int) % (max + 1)</code>,</span>
the result will be biased (not uniform). Consider the case where <code>Int = u8</code>, and a call like
<span class="display"><code>frng.int_inclusive(u8, 64 * 3)</code>.</span></p>
<p>The numbers in <code>0..64</code> are going to be twice as likely as the numbers in <code>64..(64*3)</code>, because the
last quarter of 256 range will be aliased with the first one.</p>
<p>Generating an <em>unbiased</em> number is tricky and might require drawing <em>arbitrary</em> number of bytes from
entropy. Refer to
<span class="display"><a href="https://www.pcg-random.org/posts/bounded-rands.html" class="url">https://www.pcg-random.org/posts/bounded-rands.html</a></span>
for details. I didn’t, and copy-pasted code from the Zig standard library. Use at your own risk!</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> int_inclusive</span>(frng: <span class="hl-operator">*</span>FRNG, Int: <span class="hl-type">type</span>, max: Int) Error<span class="hl-operator">!</span>Int {</span>
<span class="line">    <span class="hl-keyword">comptime</span> assert(<span class="hl-built_in">@typeInfo</span>(Int).int.signedness <span class="hl-operator">==</span> .unsigned);</span>
<span class="line">    <span class="hl-keyword">if</span> (max <span class="hl-operator">==</span> std.math.maxInt(Int)) <span class="hl-keyword">return</span> <span class="hl-keyword">try</span> frng.int(Int);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> bits = <span class="hl-built_in">@typeInfo</span>(Int).int.bits;</span>
<span class="line">    <span class="hl-keyword">const</span> less_than = max <span class="hl-operator">+</span> <span class="hl-numbers">1</span>;</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> x = <span class="hl-keyword">try</span> frng.int(Int);</span>
<span class="line">    <span class="hl-keyword">var</span> m = std.math.mulWide(Int, x, less_than);</span>
<span class="line">    <span class="hl-keyword">var</span> l: Int = <span class="hl-built_in">@truncate</span>(m);</span>
<span class="line">    <span class="hl-keyword">if</span> (l &lt; less_than) {</span>
<span class="line">        <span class="hl-keyword">var</span> t = <span class="hl-operator">-%</span>less_than;</span>
<span class="line"></span>
<span class="line">        <span class="hl-keyword">if</span> (t &gt;= less_than) {</span>
<span class="line">            t <span class="hl-operator">-=</span> less_than;</span>
<span class="line">            <span class="hl-keyword">if</span> (t &gt;= less_than) t <span class="hl-operator">%=</span> less_than;</span>
<span class="line">        }</span>
<span class="line">        <span class="hl-keyword">while</span> (l &lt; t) {</span>
<span class="line">            x = <span class="hl-keyword">try</span> frng.int(Int);</span>
<span class="line">            m = std.math.mulWide(Int, x, less_than);</span>
<span class="line">            l = <span class="hl-built_in">@truncate</span>(m);</span>
<span class="line">        }</span>
<span class="line">    }</span>
<span class="line">    <span class="hl-keyword">return</span> <span class="hl-built_in">@intCast</span>(m <span class="hl-operator">&gt;&gt;</span> bits);</span>
<span class="line">}</span></code></pre>

</figure>
<p>Now we can generate an int bounded from above and below:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> range_inclusive</span>(</span>
<span class="line">    frng: <span class="hl-operator">*</span>FRNG, Int: <span class="hl-type">type</span>,</span>
<span class="line">    min: Int, max: Int,</span>
<span class="line">) Error<span class="hl-operator">!</span>Int {</span>
<span class="line">    <span class="hl-keyword">comptime</span> assert(<span class="hl-built_in">@typeInfo</span>(Int).int.signedness <span class="hl-operator">==</span> .unsigned);</span>
<span class="line">    assert(min &lt;= max);</span>
<span class="line">    <span class="hl-keyword">return</span> min <span class="hl-operator">+</span> <span class="hl-keyword">try</span> frng.int_inclusive(Int, max <span class="hl-operator">-</span> min);</span>
<span class="line">}</span></code></pre>

</figure>
<p>Another common operation is picking a random element from a slice. If you want to return a pointer
to a element, you’ll need a <code>const</code> and <code>mut</code> versions of the function. A simpler and more general
solution is to return an index:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> index</span>(frng: <span class="hl-operator">*</span>FRNG, slice: <span class="hl-type">anytype</span>) Error<span class="hl-operator">!</span><span class="hl-type">usize</span> {</span>
<span class="line">    assert(slice.len &gt; <span class="hl-numbers">0</span>);</span>
<span class="line">    <span class="hl-keyword">return</span> <span class="hl-keyword">try</span> frng.range_inclusive(<span class="hl-type">usize</span>, <span class="hl-numbers">0</span>, slice.len <span class="hl-operator">-</span> <span class="hl-numbers">1</span>);</span>
<span class="line">}</span></code></pre>

</figure>
<p>At the call site,
<span class="display"><code>xs[try frng.index(xs)]</code></span>
doesn’t look too bad, is appropriately <code>const</code>-polymorphic, and is also usable for multiple parallel
arrays.</p>
</section>
<section id="Simulation">

<h2><a href="#Simulation">Simulation</a></h2>
<p>So far, we’ve spent about 40% of our line budget implementing a worse random number generator that
can fail with <code>OutOfEntropy</code> at any point in time. What is it good for?</p>
<p>We use it to feed our system under test with random inputs, see how it reacts, and check that it
does not crash. If we code our system to crash if anything unexpected happens and our random inputs
cover the space of all possible inputs, we get a measure of confidence that bugs will be detected in
testing.</p>
<p>For my consensus simulation, I have a <code>World</code> struct that holds a <code>FRNG</code> and a set of replicas:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> World = <span class="hl-keyword">struct</span> {</span>
<span class="line">    frng: <span class="hl-operator">*</span>FRNG,</span>
<span class="line">    replicas: []Replica,</span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line">};</span></code></pre>

</figure>
<p><code>World</code> has methods like:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> simulate_request</span>(world: <span class="hl-operator">*</span>World) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> replica = <span class="hl-keyword">try</span> world.frng.index(world.replicas);</span>
<span class="line">    <span class="hl-keyword">const</span> payload = <span class="hl-keyword">try</span> world.frng.int(<span class="hl-type">u64</span>);</span>
<span class="line"></span>
<span class="line">    world.send_payload(replica, payload);</span>
<span class="line">}</span></code></pre>

</figure>
<p>I then select which method to call at random:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> step</span>(world: <span class="hl-operator">*</span>World) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> action = <span class="hl-keyword">try</span> world.frng.weighted(.{</span>
<span class="line">        .request = <span class="hl-numbers">10</span>,</span>
<span class="line">        .message = <span class="hl-numbers">20</span>,</span>
<span class="line">        .crash = <span class="hl-numbers">1</span>,</span>
<span class="line">    });</span>
<span class="line">    <span class="hl-keyword">switch</span> (action) {</span>
<span class="line">        .request =&gt; <span class="hl-keyword">try</span> world.simulate_request(),</span>
<span class="line">        .message =&gt; { ... },</span>
<span class="line">        .crash =&gt; { ... },</span>
<span class="line">    }</span>
<span class="line">}</span></code></pre>

</figure>
<p>Here, <code>fn weighted</code> is another FRNG helper that selects an action at random, proportional to its
weight. This helper needs quite a bit more reflection machinery than we’ve seen so far:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> weighted</span>(</span>
<span class="line">    frng: <span class="hl-operator">*</span>FRNG,</span>
<span class="line">    weights: <span class="hl-type">anytype</span>,</span>
<span class="line">) Error<span class="hl-operator">!</span>std.meta.FieldEnum(<span class="hl-built_in">@TypeOf</span>(weights)) {</span>
<span class="line">    <span class="hl-keyword">const</span> fields =</span>
<span class="line">        <span class="hl-keyword">comptime</span> std.meta.fieldNames(<span class="hl-built_in">@TypeOf</span>(weights));</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> total: <span class="hl-type">u32</span> = <span class="hl-numbers">0</span>;</span>
<span class="line">    <span class="hl-keyword">inline</span> <span class="hl-keyword">for</span> (fields) <span class="hl-operator">|</span>field<span class="hl-operator">|</span></span>
<span class="line">        total <span class="hl-operator">+=</span> <span class="hl-built_in">@field</span>(weights, field);</span>
<span class="line">    assert(total &gt; <span class="hl-numbers">0</span>);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> pick = <span class="hl-keyword">try</span> frng.int_inclusive(<span class="hl-type">u64</span>, total <span class="hl-operator">-</span> <span class="hl-numbers">1</span>);</span>
<span class="line">    <span class="hl-keyword">inline</span> <span class="hl-keyword">for</span> (fields) <span class="hl-operator">|</span>field<span class="hl-operator">|</span> {</span>
<span class="line">        <span class="hl-keyword">const</span> weight = <span class="hl-built_in">@field</span>(weights, field);</span>
<span class="line">        <span class="hl-keyword">if</span> (pick &lt; weight) {</span>
<span class="line">            <span class="hl-keyword">return</span> <span class="hl-built_in">@field</span>(</span>
<span class="line">                std.meta.FieldEnum(<span class="hl-built_in">@TypeOf</span>(weights)),</span>
<span class="line">                field,</span>
<span class="line">            );</span>
<span class="line">        }</span>
<span class="line">        pick <span class="hl-operator">-=</span> weight;</span>
<span class="line">    }</span>
<span class="line">    <span class="hl-keyword">unreachable</span>;</span>
<span class="line">}</span></code></pre>

</figure>
<p><code>weights: anytype</code> is compile-time duck-typing. It means that our <code>weighted</code> function is callable
with any type, and each specific type creates a new monomorphised instance of a function. While we
don’t explicitly name the type of <code>weights</code>, we can get it as <code>@TypeOf(weights)</code>.</p>
<p><code>FieldEnum</code> is a type-level function that takes a struct type:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> S = <span class="hl-keyword">struct</span> {</span>
<span class="line">    foo: <span class="hl-type">bool</span>,</span>
<span class="line">    bar: <span class="hl-type">u32</span>,</span>
<span class="line">    baz: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span></span>
<span class="line">};</span></code></pre>

</figure>
<p>and turns it into an enum type, with a variant per-field, exactly what we want for the return type:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> E = <span class="hl-keyword">enum</span> { foo, bar, baz };</span></code></pre>

</figure>
<p>Tip: if you want to quickly learn Zig’s reflection capabilities, study the implementation of
<a href="https://codeberg.org/ziglang/zig/src/tag/0.16.0/lib/std/meta.zig"><code>std.meta</code></a> and
<a href="https://codeberg.org/ziglang/zig/src/tag/0.16.0/lib/std/enums.zig"><code>std.enums</code></a>
in Zig’s standard library.</p>
<p>The <code>@field</code> built-in function accesses a field given <code>comptime</code> field name. It’s exactly like
Python’s <code>getattr</code> / <code>setattr</code> with an extra restriction that it must be evaluated at compile time.</p>
<p>To add one more twist here, I always find it hard to figure out which weights are reasonable, and
like to generate the weights themselves at random at the start of the test:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> swarm_weights</span>(frng: <span class="hl-operator">*</span>FRNG, Weights: <span class="hl-type">type</span>) Error<span class="hl-operator">!</span>Weights {</span>
<span class="line">    <span class="hl-keyword">var</span> result: Weights = <span class="hl-literal">undefined</span>;</span>
<span class="line">    <span class="hl-keyword">inline</span> <span class="hl-keyword">for</span> (<span class="hl-keyword">comptime</span> std.meta.fieldNames(Weights)) <span class="hl-operator">|</span>field<span class="hl-operator">|</span> {</span>
<span class="line">        <span class="hl-built_in">@field</span>(result, field) = <span class="hl-keyword">try</span> frng.range_inclusive(<span class="hl-type">u32</span>, <span class="hl-numbers">1</span>, <span class="hl-numbers">100</span>);</span>
<span class="line">    }</span>
<span class="line">    <span class="hl-keyword">return</span> result;</span>
<span class="line">}</span></code></pre>

</figure>
<p>(If you feel confused here, check out
<a href="https://tigerbeetle.com/blog/2025-04-23-swarm-testing-data-structures/"><em>Swarm Testing Data Structures</em></a>)</p>
</section>
<section id="Stepping-And-Runnig">

<h2><a href="#Stepping-And-Runnig">Stepping And Runnig</a></h2>
<p>Now we have enough machinery to describe the shape of test overall:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> run_test</span>(gpa: Allocator, frng: <span class="hl-operator">*</span>FRNG) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">var</span> world = World.init(gpa, <span class="hl-operator">&amp;</span>frng) <span class="hl-keyword">catch</span> <span class="hl-operator">|</span>err<span class="hl-operator">|</span></span>
<span class="line">        <span class="hl-keyword">switch</span> (err) {</span>
<span class="line">            <span class="hl-keyword">error</span>.OutOfEntropy =&gt; <span class="hl-keyword">return</span>,</span>
<span class="line">            <span class="hl-keyword">else</span> =&gt; <span class="hl-keyword">return</span> err,</span>
<span class="line">        };</span>
<span class="line">    <span class="hl-keyword">defer</span> world.deinit(gpa);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">while</span> (<span class="hl-literal">true</span>) {</span>
<span class="line">        world.step() <span class="hl-keyword">catch</span> <span class="hl-operator">|</span>err<span class="hl-operator">|</span> <span class="hl-keyword">switch</span> (err) {</span>
<span class="line">            <span class="hl-keyword">error</span>.OutOfEntropy =&gt; <span class="hl-keyword">break</span>,</span>
<span class="line">        };</span>
<span class="line">    }</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">const</span> World = <span class="hl-keyword">struct</span> {</span>
<span class="line">    frng: <span class="hl-operator">*</span>FRNG,</span>
<span class="line">    weights: ActionWeights,</span>
<span class="line"></span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> ActionWeights = <span class="hl-keyword">struct</span> {</span>
<span class="line">        request: <span class="hl-type">u32</span>,</span>
<span class="line">        message: <span class="hl-type">u32</span>,</span>
<span class="line">        crash: <span class="hl-type">u32</span>,</span>
<span class="line">        <span class="hl-comment">// ...</span></span>
<span class="line">    };</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> init</span>(gpa: Allocator, frng: <span class="hl-operator">*</span>FRNG) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">        <span class="hl-keyword">const</span> weights = <span class="hl-keyword">try</span> frng.swarm_weights(ActionWeights);</span>
<span class="line">        <span class="hl-comment">// ...</span></span>
<span class="line">    }</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">fn</span><span class="hl-function"> step</span>(world: <span class="hl-operator">*</span>World) <span class="hl-keyword">error</span>{OutOfEntropy}<span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">        <span class="hl-keyword">const</span> action = <span class="hl-keyword">try</span> world.frng.weighted(world.weights);</span>
<span class="line">        <span class="hl-keyword">switch</span> (action) {</span>
<span class="line">            .request =&gt; { ... },</span>
<span class="line">            <span class="hl-comment">// ...</span></span>
<span class="line">        }</span>
<span class="line">    }</span>
<span class="line">};</span></code></pre>

</figure>
<p>A test needs an <code>FRNG</code> (which ultimately determines the outcome) and an General Purpose Allocator
for the <code>World</code>. We start by creating a simulated <code>World</code> with random action weights. If <code>FRNG</code>
entropy is very low, we can run out of entropy even at this stage. We assume that the code is
innocent until proven guilty — if we don’t have enough entropy to find a bug, this particular test
returns success. Don’t worry, we’ll make sure that we have enough entropy elsewhere.</p>
<p>We use <code>catch |err| switch(err)</code> to peel off <code>OutOfEntropy</code> error. I find that, whenever I handle
errors in Zig, very often I want to discharge just a single error from the error set. I wish I could
use parenthesis with a <code>catch</code>:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-comment">// NOT ACTUALY ZIG :(</span></span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">var</span> world = <span class="hl-keyword">try</span> World.init(gpa, <span class="hl-operator">&amp;</span>frng)</span>
<span class="line">    <span class="hl-keyword">catch</span> (<span class="hl-keyword">error</span>.OutOfEntropy) <span class="hl-keyword">return</span>;</span></code></pre>

</figure>
<p>Anyway, having created the <code>World</code>, we step through it while we still have entropy left. If any step
detects an internal inconsistency, the entire <code>World</code> crashes with an assertion failure. If we got
to the end of <code>while(true)</code> loop, we know that at least that particular slice of entropy didn’t
uncover anything suspicious.</p>
<p>Notice what <em>isn’t</em> there. We aren’t generating a complete list of actions up-front. Rather, we make
random decisions as we go, and can freely use the current state of the <code>World</code> to construct a menu
of possible choices (e.g., when sending a message, we can consider only not currently crashed
replicas).</p>
</section>
<section id="Binary-Search-the-Answer">

<h2><a href="#Binary-Search-the-Answer">Binary Search the Answer</a></h2>
<p>And here we can finally see the reason why we bothered writing a custom <em>Finite</em> PRNG, rather than
using an off-the-shelf one. The amount of entropy in FRNG defines the complexity of the test. The
fewer random bytes we start with, the faster we exit the step loop. And this gives us an ability to
minimize test cases essentially for free.</p>
<p>Suppose you know that a particular entropy slice makes the test fail (cluster enters split brain at
the millionth step). Let’s say that the slice was 16KiB. The obvious next step is to see if just
8KiB would be enough to crash it. And, if 8KiB isn’t, than perhaps 12KiB?</p>
<p>You can <strong><strong>binary search</strong></strong> the minimal amount of entropy that’s enough for the test to fail. And this
works for <em>any</em> test, it doesn’t have to be a distributed system. If you can write the code to
generate <em>your</em> inputs randomly, you can measure complexity of each particular input by measuring
how many random bytes were drawn in its construction.</p>
<p>And now the hilarious part — of course it seems that the way to minimize entropy is to start with
a particular failing slice and apply genetic-algorithm mutations to it. But a much simpler approach
seems to work in practice — just generated a fresh, shorter entropy slice. If you found <em>some</em>
failure at random, then you should be able to randomly stumble into a smaller failing example, if
one exists — there are much fewer small examples, so finding a failing one becomes easier when the
<code>size</code> goes down!</p>
</section>
<section id="The-Searcher">

<h2><a href="#The-Searcher">The Searcher</a></h2>
<p>The problem with binary searching for failing entropy is that a tripped assertion crashes the
program. There’s no unwinding in Zig. For this reason, we’ll move the search code to a different
process. So a single test will be a binary with a <code>main</code> function, that takes entropy on <code>stdin</code>.</p>
<p>Zig’s new juicy main makes writing this easier than in any previous versions of Zig :D</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> main</span>(init: std.process.Init) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> gpa = init.gpa;</span>
<span class="line">    <span class="hl-keyword">const</span> io = init.io;</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> stdin_reader = std.Io.File.stdin().reader(io, <span class="hl-operator">&amp;</span>.{});</span>
<span class="line">    <span class="hl-keyword">const</span> entropy = <span class="hl-keyword">try</span> stdin_reader.interface</span>
<span class="line">        .allocRemaining(gpa, .unlimited);</span>
<span class="line">    <span class="hl-keyword">defer</span> gpa.free(entropy);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> frng = FRNG.init(entropy);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> world = World.init(gpa, <span class="hl-operator">&amp;</span>frng, .{}) <span class="hl-keyword">catch</span> <span class="hl-operator">|</span>err<span class="hl-operator">|</span></span>
<span class="line">        <span class="hl-keyword">switch</span> (err) {</span>
<span class="line">            <span class="hl-keyword">error</span>.OutOfEntropy =&gt; <span class="hl-keyword">return</span>,</span>
<span class="line">            <span class="hl-keyword">else</span> =&gt; <span class="hl-keyword">return</span> err,</span>
<span class="line">        };</span>
<span class="line">    <span class="hl-keyword">defer</span> world.deinit(gpa);</span>
<span class="line"></span>
<span class="line">    world.run();</span>
<span class="line">}</span></code></pre>

</figure>
<p>Main gets <code>Init</code> as an argument, which provides access to things like command line arguments,
default allocator and a default <code>Io</code> implementation. These days, Zig eschews global ambient IO
capabilities, and requires threading an Io instance whenever we need to make a syscall. Here,
we need Io to read stdin.</p>
<p>Now we will implement a harness to call this main. This will be <code>FRNG.Driver</code>:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">const</span> Driver = <span class="hl-keyword">struct</span> {</span>
<span class="line">    io: std.Io,</span>
<span class="line">    sut: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span>,</span>
<span class="line">    buffer: []<span class="hl-type">u8</span>,</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> log = std.log;</span>
<span class="line">};</span></code></pre>

</figure>
<p>It will be spawning external processes, so it’ll need an <code>Io</code>. We also need a path to an executable
with a test main function, a System Under Test. And we’ll need a buffer to hold the entropy. This
driver will be communicating successes and failures to the users, so we also prepare a <code>log</code> for
textual output.</p>
<p>How we get entropy to feed into <code>sut</code>? Because we are only interested in entropy size, we won’t be
storing the actual entropy bytes, and instead will generate it from a <code>u64</code> seed. In other words,
just two numbers, entropy size and seed, are needed to reproduce a single run of the test:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> run_once</span>(driver: Driver, options: <span class="hl-keyword">struct</span> {</span>
<span class="line">    size: <span class="hl-type">u32</span>,</span>
<span class="line">    seed: <span class="hl-type">u64</span>,</span>
<span class="line">    quiet: <span class="hl-type">bool</span>,</span>
<span class="line">}) <span class="hl-operator">!</span><span class="hl-keyword">enum</span> { pass, fail } {</span>
<span class="line">    assert(options.size &lt;= driver.buffer.len);</span>
<span class="line">    <span class="hl-keyword">const</span> entropy = driver.buffer[<span class="hl-numbers">0</span>..options.size];</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> rng = std.Random.DefaultPrng.init(options.seed);</span>
<span class="line">    rng.random().bytes(entropy);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> child = <span class="hl-keyword">try</span> std.process.spawn(driver.io, .{</span>
<span class="line">        .argv = <span class="hl-operator">&amp;</span>.{driver.sut},</span>
<span class="line">        .stdin = .pipe,</span>
<span class="line">        .stderr = <span class="hl-keyword">if</span> (options.quiet) .ignore <span class="hl-keyword">else</span> .inherit,</span>
<span class="line">    });</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">try</span> child.stdin.?.writeStreamingAll(driver.io, entropy);</span>
<span class="line">    child.stdin.?.close(driver.io);</span>
<span class="line">    child.stdin = <span class="hl-literal">null</span>;</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> term = <span class="hl-keyword">try</span> child.wait(driver.io);</span>
<span class="line">    <span class="hl-keyword">return</span> <span class="hl-keyword">if</span> (success(term)) .pass <span class="hl-keyword">else</span> .fail;</span>
<span class="line">}</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> success</span>(term: std.process.Child.Term) <span class="hl-type">bool</span> {</span>
<span class="line">    <span class="hl-keyword">return</span> term <span class="hl-operator">==</span> .exited <span class="hl-keyword">and</span> term.exited <span class="hl-operator">==</span> <span class="hl-numbers">0</span>;</span>
<span class="line">}</span></code></pre>

</figure>
<p>We use default deterministic PRNG to expand our short seed into entropy slice of the required size.
Then we spawn <code>sut</code> proces, feeding the resulting entropy via stdin. Closing child’s stdin signals
the end of entropy. We then return either <code>.pass</code> or <code>.fail</code> depending on child’s exit code. So, both
explicit errors and crashes will be recognized as failures.</p>
<p>Next, we implement the logic for checking if a particular seed size is sufficient to find a failure.
Of course, we won’t be able to say that for sure in a finite amount of time, so we’ll settle for some
user-specified amount of retries:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> run_multiple</span>(driver: Driver, options: <span class="hl-keyword">struct</span> {</span>
<span class="line">    size: <span class="hl-type">u32</span>,</span>
<span class="line">    attempts: <span class="hl-type">u32</span>,</span>
<span class="line">}) <span class="hl-operator">!</span><span class="hl-keyword">union</span>(<span class="hl-keyword">enum</span>) { pass, fail: <span class="hl-type">u64</span> } {</span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line">}</span></code></pre>

</figure>
<p>The user passes us the number of <code>attempts</code> to make, and we return <code>.pass</code> if they all were
successfull, or a specific failing seed if we found one:</p>

<figure class="code-block">


<pre><code><span class="line">assert(options.size &lt;= driver.buffer.len);</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">for</span> (<span class="hl-numbers">0</span>..options.attempts) <span class="hl-operator">|</span>_<span class="hl-operator">|</span> {</span>
<span class="line">    <span class="hl-keyword">var</span> seed: <span class="hl-type">u64</span> = <span class="hl-literal">undefined</span>;</span>
<span class="line">    driver.io.random(<span class="hl-built_in">@ptrCast</span>(<span class="hl-operator">&amp;</span>seed));</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> outcome = <span class="hl-keyword">try</span> driver.run_once(.{</span>
<span class="line">        .seed = seed,</span>
<span class="line">        .size = options.size,</span>
<span class="line">        .quiet = <span class="hl-literal">true</span>,</span>
<span class="line">    });</span>
<span class="line">    <span class="hl-keyword">switch</span> (outcome) {</span>
<span class="line">        .fail =&gt; <span class="hl-keyword">return</span> .{ .fail = seed },</span>
<span class="line">        .pass =&gt; {},</span>
<span class="line">    }</span>
<span class="line">}</span>
<span class="line"><span class="hl-keyword">return</span> .pass;</span></code></pre>

</figure>
<p>To generate a real seed we need “true” cryptographic non-deterministic randomness, which is provided
by <code>io.random</code>.</p>
<p>Finally, the search for the size:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">fn</span><span class="hl-function"> search</span>(driver: Driver, options: <span class="hl-keyword">struct</span> {</span>
<span class="line">    attempts: <span class="hl-type">u32</span> = <span class="hl-numbers">100</span>,</span>
<span class="line">}) <span class="hl-operator">!</span><span class="hl-keyword">union</span>(<span class="hl-keyword">enum</span>) {</span>
<span class="line">    pass,</span>
<span class="line">    fail: <span class="hl-keyword">struct</span> { size: <span class="hl-type">u32</span>, seed: <span class="hl-type">u64</span> },</span>
<span class="line">} {</span>
<span class="line">    <span class="hl-comment">// ...</span></span>
<span class="line">}</span></code></pre>

</figure>
<p>Here, we are going to find a smallest entropy size that crashes <code>sut</code>. If we succeed, we return the
seed and the size. The upper bound for the size is the space available in the pre-allocated entropy
buffer.</p>
<p>The search loop is essentially a binary search, with a twist — rather than using dichotomy on the
<code>size</code> directly, we will be doubling a <code>step</code> we use to change the size between iterations.</p>
<p>That is, we start with a small size and step, and, on every iteration, double the step and add it to
the size, until we hit a failure (or run out of buffer for the entropy).</p>
<p>Once we found a failure, we continue the search in the other direction — halving the step and
subtracting it from the <code>size</code>, keeping the smaller <code>size</code> if it still fails.</p>
<p>On each step, we log the current size and outcome, and report the smallest failing size at the end.</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">var</span> found_size: ?<span class="hl-type">u32</span> = <span class="hl-literal">null</span>;</span>
<span class="line"><span class="hl-keyword">var</span> found_seed: ?<span class="hl-type">u64</span> = <span class="hl-literal">null</span>;</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">var</span> pass: <span class="hl-type">bool</span> = <span class="hl-literal">true</span>;</span>
<span class="line"><span class="hl-keyword">var</span> size: <span class="hl-type">u32</span> = <span class="hl-numbers">16</span>;</span>
<span class="line"><span class="hl-keyword">var</span> step: <span class="hl-type">u32</span> = <span class="hl-numbers">16</span>;</span>
<span class="line"><span class="hl-keyword">for</span> (<span class="hl-numbers">0</span>..<span class="hl-numbers">1024</span>) <span class="hl-operator">|</span>_<span class="hl-operator">|</span> {</span>
<span class="line">    <span class="hl-keyword">if</span> (step <span class="hl-operator">==</span> <span class="hl-numbers">0</span>) <span class="hl-keyword">break</span>;</span>
<span class="line">    <span class="hl-keyword">const</span> size_next = <span class="hl-keyword">if</span> (pass) size <span class="hl-operator">+</span> step <span class="hl-keyword">else</span> size <span class="hl-operator">-</span><span class="hl-operator">|</span> step;</span>
<span class="line">    <span class="hl-keyword">if</span> (size &gt; driver.buffer.len) <span class="hl-keyword">break</span>;</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> outcome = <span class="hl-keyword">try</span> driver.run_multiple(.{</span>
<span class="line">        .size = size_next,</span>
<span class="line">        .attempts = options.attempts,</span>
<span class="line">    });</span>
<span class="line">    <span class="hl-keyword">switch</span> (outcome) {</span>
<span class="line">        .pass =&gt; log.info(<span class="hl-string">&quot;pass: size={}&quot;</span>, .{size_next}),</span>
<span class="line">        .fail =&gt; <span class="hl-operator">|</span>seed<span class="hl-operator">|</span> {</span>
<span class="line">            found_size = size_next;</span>
<span class="line">            found_seed = seed;</span>
<span class="line">            log.err(<span class="hl-string">&quot;fail: size={} seed={}&quot;</span>, .{ size_next, seed });</span>
<span class="line">        },</span>
<span class="line">    }</span>
<span class="line">    <span class="hl-keyword">const</span> pass_next = (outcome <span class="hl-operator">==</span> .pass);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">if</span> (pass <span class="hl-keyword">and</span> pass_next) {</span>
<span class="line">        step <span class="hl-operator">*=</span> <span class="hl-numbers">2</span>;</span>
<span class="line">    } <span class="hl-keyword">else</span> <span class="hl-keyword">if</span> (<span class="hl-operator">!</span>pass <span class="hl-keyword">and</span> <span class="hl-operator">!</span>pass_next) {</span>
<span class="line">        <span class="hl-comment">// Keep the step.</span></span>
<span class="line">    } <span class="hl-keyword">else</span> {</span>
<span class="line">        step <span class="hl-operator">/=</span> <span class="hl-numbers">2</span>;</span>
<span class="line">    }</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">if</span> (pass <span class="hl-keyword">or</span> <span class="hl-operator">!</span>pass_next) {</span>
<span class="line">        size = size_next;</span>
<span class="line">        pass = pass_next;</span>
<span class="line">    }</span>
<span class="line">} <span class="hl-keyword">else</span> <span class="hl-built_in">@panic</span>(<span class="hl-string">&quot;safety counter&quot;</span>);</span>
<span class="line"></span>
<span class="line"><span class="hl-keyword">if</span> (found_size <span class="hl-operator">==</span> <span class="hl-literal">null</span>) <span class="hl-keyword">return</span> .pass;</span>
<span class="line"><span class="hl-keyword">return</span> .{ .fail = .{</span>
<span class="line">    .size = found_size.?,</span>
<span class="line">    .seed = found_seed.?,</span>
<span class="line">} };</span></code></pre>

</figure>
<p>Finally, we wrap Driver’s functionality into main that works in two modes — either reproduces a
given failure from seed and size, or searches for a minimal failure:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">pub</span> <span class="hl-keyword">fn</span><span class="hl-function"> main</span>(</span>
<span class="line">    gpa: std.mem.Allocator,</span>
<span class="line">    io: std.Io,</span>
<span class="line">    sut: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span>,</span>
<span class="line">    operation: <span class="hl-keyword">union</span>(<span class="hl-keyword">enum</span>) {</span>
<span class="line">        replay: <span class="hl-keyword">struct</span> { size: <span class="hl-type">u32</span>, seed: <span class="hl-type">u64</span> },</span>
<span class="line">        search: <span class="hl-keyword">struct</span> {</span>
<span class="line">            attempts: <span class="hl-type">u32</span> = <span class="hl-numbers">100</span>,</span>
<span class="line">            size_max: <span class="hl-type">u32</span> = <span class="hl-numbers">4</span> <span class="hl-operator">*</span> <span class="hl-numbers">1024</span> <span class="hl-operator">*</span> <span class="hl-numbers">1024</span>,</span>
<span class="line">        },</span>
<span class="line">    },</span>
<span class="line">) <span class="hl-operator">!</span><span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> size_max = <span class="hl-keyword">switch</span> (operation) {</span>
<span class="line">        .replay =&gt; <span class="hl-operator">|</span>options<span class="hl-operator">|</span> options.size,</span>
<span class="line">        .search =&gt; <span class="hl-operator">|</span>options<span class="hl-operator">|</span> options.size_max,</span>
<span class="line">    };</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">const</span> buffer = <span class="hl-keyword">try</span> gpa.alloc(<span class="hl-type">u8</span>, size_max);</span>
<span class="line">    <span class="hl-keyword">defer</span> gpa.free(buffer);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> driver: Driver = .{</span>
<span class="line">        .io = io,</span>
<span class="line">        .buffer = buffer,</span>
<span class="line">        .sut = sut,</span>
<span class="line">    };</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">switch</span> (operation) {</span>
<span class="line">        .replay =&gt; <span class="hl-operator">|</span>options<span class="hl-operator">|</span> {</span>
<span class="line">            <span class="hl-keyword">const</span> outcome = <span class="hl-keyword">try</span> driver.run_once(.{</span>
<span class="line">                .size = options.size,</span>
<span class="line">                .seed = options.seed,</span>
<span class="line">                .quiet = <span class="hl-literal">false</span>,</span>
<span class="line">            });</span>
<span class="line">            log.info(<span class="hl-string">&quot;{t}&quot;</span>, .{outcome});</span>
<span class="line">        },</span>
<span class="line">        .search =&gt; <span class="hl-operator">|</span>options<span class="hl-operator">|</span> {</span>
<span class="line">            <span class="hl-keyword">const</span> outcome = <span class="hl-keyword">try</span> driver.search(.{</span>
<span class="line">                .attempts = options.attempts,</span>
<span class="line">             });</span>
<span class="line">            <span class="hl-keyword">switch</span> (outcome) {</span>
<span class="line">                .pass =&gt; log.info(<span class="hl-string">&quot;ok&quot;</span>, .{}),</span>
<span class="line">                .fail =&gt; <span class="hl-operator">|</span>fail<span class="hl-operator">|</span> {</span>
<span class="line">                    log.err(<span class="hl-string">&quot;minimized size={} seed={}&quot;</span>, .{</span>
<span class="line">                        fail.size, fail.seed,</span>
<span class="line">                     });</span>
<span class="line">                },</span>
<span class="line">            }</span>
<span class="line">        },</span>
<span class="line">    }</span>
<span class="line">}</span></code></pre>

</figure>
<p>Running the search routine looks like this in a terminal:</p>
<figure>
<script src="https://asciinema.org/a/954634.js" id="asciicast-954634" async="true"></script>
</figure>
<p>Those final seed&amp;size can then be used for <code>.replay</code>, giving you a minimal reproducible failure for
debugging!</p>
<p>This … of course doesn’t look too exciting without visualizing a <em>specific</em> bug we can find this
way, but the problem there is that interesting examples of systems to test in this way usually take
more than 256 lines to implement. So I’ll leave it to your imagination, but you get the idea: if you
can make a system fail under a “random” input, you can also systematically search the space of all
inputs for the smallest counter-example, <em>without</em> adding knowledge about the system to the
searcher. <a href="https://matklad.github.io/2024/07/05/properly-testing-concurrent-data-structures.html">This article</a>
also provides a concrete (but somewhat verbose) example.</p>
<p>Here’s the full code:</p>
<p><a href="https://gist.github.com/matklad/343d13547c8bfe9af310e2ca2fbfe109" class="url">https://gist.github.com/matklad/343d13547c8bfe9af310e2ca2fbfe109</a></p>
</section>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Consensus Board Game</title>
    <link
      href="https://matklad.github.io/2026/03/19/consensus-board-game.html"
      rel="alternate"
      type="text/html"
      title="Consensus Board Game"
    />
    <published>2026-03-19T00:00:00+00:00</published>
    <updated>2026-03-19T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/03/19/consensus-board-game</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[I have an early adulthood trauma from struggling to understand consensus amidst a myriad of poor explanations. I am overcompensating for that by adding my own attempts to the fray. Today, I want to draw a series of pictures which could be helpful. You can see this post as a set of missing illustrations for Notes on Paxos, or, alternatively, you can view that post as a more formal narrative counter-part for the present one.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/03/19/consensus-board-game.html"
    >
      <![CDATA[
<header>
  <h1>Consensus Board Game</h1>
  <time class="meta" datetime="2026-03-19">Mar 19, 2026</time>
</header>
<p>I have an early adulthood trauma from struggling to understand consensus amidst a myriad of poor
explanations. I am overcompensating for that by adding my own attempts to the fray. Today, I
want to draw a series of pictures which could be helpful. You can see this post as a set of
missing illustrations for
<a href="https://matklad.github.io/2020/11/01/notes-on-paxos.html"><em>Notes on Paxos</em></a>, or, alternatively,
you can view <em>that</em> post as a more formal narrative counter-part for the present one.</p>
<p>The idea comes from my <a href="https://tigerbeetle.com/blog/2025-11-22-mathematics-of-consensus/">mathematics of consensus</a>
lecture, with versions in <a href="https://www.youtube.com/watch?v=thY12Wnrmyw">English</a> and
<a href="https://www.youtube.com/watch?v=h8nj65jvXl0">Russian</a>.</p>
<section id="The-Preamble">

<h2><a href="#The-Preamble">The Preamble</a></h2>
<p>I am going to <em>aggressively</em> hand wave the details away, please refer to
<a href="https://matklad.github.io/2020/11/01/notes-on-paxos.html"><em>Notes</em></a> for filling in the blanks.</p>
<p>And, before we begin, I want to stress again that here I am focusing strictly on the <em>mathematics</em>
behind the algorithm, on the logical structure of the universe that makes some things impossible, and
others doable. Consensus is but a small part of the engineering behind real data management systems,
and I might do something about <em>pragmatics</em> of consensus at some point, just not today ;)</p>
</section>
<section id="The-Problem">

<h2><a href="#The-Problem">The Problem</a></h2>
<p>There’s a committee of five members that tries to choose a color for a bike shed, but
the committee members are not entirely reliable. We want to arrive at a decision even if some
members of the committee are absent.</p>
</section>
<section id="The-Vote">

<h2><a href="#The-Vote">The Vote</a></h2>
<p>The fundamental idea underpinning consensus is simple majority vote. If R0, … R4 are the five
committee members, we can use the following board to record the votes:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/blank.svg">
</figure>
<p>A successful vote looks like this:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/red-wins.svg">
</figure>
<p>Here, red collected 3 out of 5 votes and wins. Note that R4 hasn’t voted yet. It might, or might not
do so eventually, but that won’t affect the outcome.</p>
<p>The problem with voting is that it can get stuck like this:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/stuck.svg">
</figure>
<p>Here, we have two votes for red, two votes for blue, but the potential tie-breaker, R4,  voted for
green, the rascal!</p>
<p>To solve split vote, we are going to designate R0 as the committee’s leader, make it choose the
color, and allow others only to approve. Note that meaningful voting still takes place, as
someone might abstain from voting — you need at least 50% turnout for the vote to be complete:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/leader.svg">
</figure>
<p>Here, R0, the leader (marked with yellow napoleonic bicorne), choose red, R2 and R3 acquiesced, so
the red “won”, even as R1 and R4 abstained (x signifies absence of a vote).</p>
<p>The problem with <em>this</em> is that our designated leader might be unavailable itself:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/leader-dead.svg">
</figure>
</section>
<section id="The-Board">

<h2><a href="#The-Board">The Board</a></h2>
<p>Which brings us to the central illustration that I wanted to share. What are we going to do now is
to <em>multiply</em> our voting. Instead of conducting just one vote with a designated leader, the
committee will conduct a series of concurrent votes, where the leaders rotate in round-robin
pattern. This gives rise to the following half-infinite 2D board on which the game of consensus is
played:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/board-blank.svg">
</figure>
<p>Each column plays independently. If you are a leader in a column, and your cell is blank, you can
choose whatever color. If you are a follower, you need to wait until column’s leader decision, and
then you can either fill the same color, or you can abstain. After several rounds the board might
end up looking like this:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/board-filled.svg">
</figure>
<p>The benefit of our 2D setup is that, if any committee member is unavailable, <em>their</em> columns might
get stuck, but, as long as the majority is available, some column somewhere might still complete.
The drawback is that, while individual column’s decision is clear and unambiguous, the outcome of the
board as whole is undefined. In the above example, there’s a column where red wins, and a column
where blue wins.</p>
<p>So what we are going to do is to scrap the above board as invalid, and instead <em>require</em> that any
two columns that achieved majorities <em>must</em> agree on the color. In other words, the outcome of the
entire board is the outcome of any of its columns, whichever finishes first, and the safety
condition is that no two colors can reach majorities in different columns.</p>
<p>Let’s take a few steps back when the board wasn’t yet hosed, and try to think about the choice of
the next move from the perspective of R3:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/board-choice.svg">
</figure>
<p>As R3 and the leader for your column, you need to pick a color which won’t conflict with any past
or <em>future</em> decisions in other columns. Given that there are some greens and blues already, it
<em>feels</em> like maybe you shouldn’t pick red… But it could be the case that the three partially
filled columns won’t move anywhere in the future, and the first column gets a solid red line! Tough
choices! You need to worry about the future <em>and</em> the infinite number of columns to your right!</p>
<p>Luckily, the problem can be made much easier if we assume that everyone plays by the same rules, in
which case it’s enough to only worry about the columns to your left. Suppose that you, and everyone
else is carefully choosing their moves to not conflict with the columns to the left. Then, if you
chose red, your column wins, and subsequently some buffoon on the right chooses green, it’s <em>their</em>
problem, because you are to their left.</p>
<p>So let’s just focus on the left part of the board. Again, it seems like blue or green might be good
bets, as they are already present on the board, but there’s a chance that the first column will
eventually vote for red. To prevent that, what we are going to do is to collect a majority of
participants (R0, R2, R3) and require them to commit to <em>not</em> voting in the first columns. Actually,
for that matter, let’s prevent them from voting in <em>any</em> column to the left:</p>

<figure>

<img alt="" src="/assets/2026-03-19-consensus-board-game/board-x.svg">
</figure>
<p>Here, you asked R0, R2 and R3 to abstain from casting further votes in the first three columns,
signified by black x. With this picture, we can now be sure that red can not win in the first column
— no color can win there, because only two out of the five votes are available there!</p>
<p>Still, we have the choice between green and blue, which one should we pick? The answer is the
rightmost. R2, the participant that picked blue in the column to our immediate left, was executing
the same algorithm. If they picked blue, they did it because they knew for certain that the second
column can’t eventually vote for green. R2 got a different majority of participants to abstain from
voting in the second column, and, while we, as R3, don’t know which majority that was, we know that
it exists because we know that R2 did pick blue, and we assume fair play.</p>
<hr>
<p>That’s all for today, that’s the trick that makes consensus click, in the abstract. In a full
distributed system the situation is more complicated. Each participant only sees its own row, the
board as a whole remains concealed. Participants can learn something about others’ state by
communicating, but the knowledge isn’t strongly anchored at time. By the time a response is received
the answer could as well be obsolete. And yet, the above birds-eye view can be implemented in a few
exchanges of messages.</p>
<p>Please see the
<a href="https://matklad.github.io/2020/11/01/notes-on-paxos.html"><em>Notes</em></a>
for further details.</p>
</section>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">JJ LSP Follow Up</title>
    <link
      href="https://matklad.github.io/2026/03/05/jj-lsp-followup.html"
      rel="alternate"
      type="text/html"
      title="JJ LSP Follow Up"
    />
    <published>2026-03-05T00:00:00+00:00</published>
    <updated>2026-03-05T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/03/05/jj-lsp-followup</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[In Majjit LSP, I described an idea of implementing Magit style UX for jj once and for all, leveraging LSP protocol. I've learned today that the upcoming 3.18 version of LSP has a feature to make this massively less hacky: Text Document Content Request]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/03/05/jj-lsp-followup.html"
    >
      <![CDATA[
<header>
  <h1>JJ LSP Follow Up</h1>
  <time class="meta" datetime="2026-03-05">Mar 5, 2026</time>
</header>
<p>In <a href="https://matklad.github.io/2024/12/13/majjit-lsp.html"><em>Majjit LSP</em></a>, I described an idea of
implementing <a href="https://magit.vc">Magit</a> style UX for <a href="https://www.jj-vcs.dev">jj</a> once and for all, leveraging
LSP protocol. I’ve learned today that the upcoming 3.18 version of LSP has a feature to make this
massively less hacky:
<a href="https://microsoft.github.io/language-server-protocol/specifications/lsp/3.18/specification/#workspace_textDocumentContent" class="display"><em>Text Document Content Request</em></a></p>
<p>LSP can now provide virtual documents, which aren’t actually materialized on disk. So this:</p>

<figure>

<img alt="" src="https://github.com/user-attachments/assets/f65cedf9-5fa8-4506-b8bb-2e55e1ee1913" width="856" height="620">
</figure>
<p>can now be such a virtual document, where highlighting is provided by semantic tokens, things like
“check out this commit” are code actions, and “goto definition” jumps from the diff in the virtual
file to a real file in the working tree.</p>
<p>Exciting!</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Against Query Based Compilers</title>
    <link
      href="https://matklad.github.io/2026/02/25/against-query-based-compilers.html"
      rel="alternate"
      type="text/html"
      title="Against Query Based Compilers"
    />
    <published>2026-02-25T00:00:00+00:00</published>
    <updated>2026-02-25T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/02/25/against-query-based-compilers</id>
    <author><name>Alex Kladov</name></author>
    <summary type="html">
      <![CDATA[Query based compilers are all the rage these days, so it feels only appropriate to chart some treacherous shoals in those waters.]]>
    </summary>
    <content
      type="html"
      xml:base="https://matklad.github.io/2026/02/25/against-query-based-compilers.html"
    >
      <![CDATA[
<header>
  <h1>Against Query Based Compilers</h1>
  <time class="meta" datetime="2026-02-25">Feb 25, 2026</time>
</header>
<p><a href="https://thunderseethe.dev/posts/compiler-education-deserves-a-revoluation/">Query based compilers are all the rage</a>
these days, so it feels only appropriate to chart some treacherous shoals in those waters.</p>
<p>A query-based compiler is a straightforward application of the idea of incremental computations to,
you guessed it, compiling. A compiler is just a simple text transformation program, implemented as a
lot of functions. You could visualize a <em>run</em> of a compiler on a particular input source code as a
graph of function calls:</p>

<figure>

<img alt="" src="/assets/2026-02-25-against-query-based-compilers/1.svg">
</figure>
<p>Here, schematically, squares are inputs like file text or compiler’s command line arguments, <code>g</code> is
an intermediate function (e.g, type checking), which is called twice, with different arguments, and
<code>f</code> and <code>h</code> are top-level functions (compile executable, or compute completions for LSP).</p>
<p>Looking at this picture, it’s obvious how to make our compiler “incremental” — if an input changes,
it’s enough to re-compute only the results on path from the changed input to the root “query”:</p>

<figure>

<img alt="" src="/assets/2026-02-25-against-query-based-compilers/2.svg">
</figure>
<p>A little more thinking, and you can derive “early cutoff” optimization:</p>

<figure>

<img alt="" src="/assets/2026-02-25-against-query-based-compilers/3.svg">
</figure>
<p>If an input to the function changes, but its result doesn’t (e.g, function type is not affected by
whitespace change), you can stop change propagation early.</p>
<p>And that’s … basically it. The beauty of the scheme is its silvery-bullety hue — it can be
applied without thinking to any computation, and, with a touch of meta programming, you won’t even
have to change code of the compiler significantly.</p>
<p><a href="https://simon.peytonjones.org/assets/pdfs/build-systems-jfp.pdf"><em>Build Systems à la Carte</em></a> is the
canonical paper to read here. In a build system, a query is an opaque process whose inputs and
outputs are file. In a query-based compiler, queries are just functions.</p>
<hr>
<p>The reason why we want this in the first place is incremental compilation — in IDE context
specifically, the compiler needs to react to a stream of tiny edits, and its time budget is about
100ms. Big-O thinking is useful here: the time to react to the change should be proportional to the
size of the change, and not the overall size of the codebase. O(1) change leads to O(1) update
of the O(N) codebase.</p>
<p>Similar big-O thinking also demonstrates the principal limitation of the scheme — the update work
can’t be smaller than the change in the result.</p>
<p>An example. Suppose our “compiler” makes a phrase upper-case:</p>

<figure class="code-block">


<pre><code><span class="line">compile(&quot;hello world&quot;) == &quot;HELLO WORLD&quot;</span></code></pre>

</figure>
<p>This is easy to incrementalize, as changing a few letters in the input changes only a few letters in
the output:</p>

<figure class="code-block">


<pre><code><span class="line">compile(&quot;hallo world&quot;) == &quot;HALLO WORLD&quot;</span></code></pre>

</figure>
<p>But suppose now our “compiler” is a hashing or encryption function:</p>

<figure class="code-block">


<pre><code><span class="line">compile(&quot;hello world&quot;) == &quot;a948904f2f0&quot;</span>
<span class="line">compile(&quot;hallo world&quot;) == &quot;a7336983eca&quot;</span></code></pre>

</figure>
<p>This is provably impossible to make usefully incremental. The encryption <em>can</em> be implemented
as a graph of function calls, and you <em>can</em> apply the general incremental recipe to it. It just
won’t be very fast.</p>
<p>The reason for that is the avalanche property — for good encryption, a change in any bit of input
should flip roughly half of the bits of the output. So just the work of changing the output
(completely ignoring the work to compute what needs to be changed) is O(N), not O(1).</p>

<figure class="blockquote">
<blockquote><p>The effectiveness of query-based compiler is limited by
the dependency structure of the source language.</p>
</blockquote>

</figure>
<p>A particularly nasty effect here is that even if you have only <em>potential</em> avalanche, where a
certain kind of change <em>could</em> affect large fraction of the output, even if it usually doesn’t, your
incremental engine likely will spend some CPU time or memory to confirm the absence of dependency.</p>
<hr>
<p>In my</p>
<p><span class="display"><a href="https://rust-analyzer.github.io/blog/2020/07/20/three-architectures-for-responsive-ide.html"><em>Three Architectures For Responsive IDE</em></a>,</span>
query-based compilation is presented as a third, fall-back option. I still think that that’s
basically true: as a language designer, I think it’s worth listening to your inner
<a href="https://grugbrain.dev">Grug</a> and push the need for queries as far down the compilation pipeline as
possible, sticking to more direct approaches. <em>Not</em> doing queries is simpler, faster, and simpler to
make faster (profiling a query-based compiler is a special genre of hurdle racing).</p>
<p>Zig and Rust provide for a nice comparison. In Zig, every file can be parsed completely in
isolation, so compilation starts by parsing all files independently and in parallel. Because in Zig
every name needs to be explicitly declared (there’s no <code>use *</code>), name resolution also can run on a
per-file basis, without queries. Zig goes even further, and directly converts untyped AST into IR,
emitting a whole bunch of errors in the process (e.g, “<code>var</code> doesn’t need to be mutable”). See
<span class="display"><a href="https://mitchellh.com/zig/astgen"><em>Zig AstGen: AST =&gt; ZIR</em></a></span>
for details. By the time compiler gets to tracked queries, the data it has to work with is already
pretty far from the raw source code, but only because Zig <em>language</em> is carefully designed to allow
this.</p>
<p>In contrast, you can’t really parse a file in Rust. Rust macros generate new source code, so parsing
can’t be finished until all the macros are expanded. Expanding macros requires name resolution,
which, in Rust, is a crate-wide, rather than a file-wide operation. Its a fundamental property of
the language that typing something in <code>a.rs</code> can change parsing results for <code>b.rs</code>, and that forces
fine-grained dependency tracking and invalidation to the very beginning of the front-end.</p>
<p>Similarly, the nature of the trait system is such that <code>impl</code> blocks relevant to a particular method
call can be found almost anywhere. For every trait method call, you get a dependency on the <code>impl</code>
block that supplies the implementation, but you <em>also</em> get a dependency on non-existence of
conflicting <code>impl</code>s in every other file!</p>
<p>Again, refer to the
<a href="https://rust-analyzer.github.io/blog/2020/07/20/three-architectures-for-responsive-ide.html"><em>Three Architectures</em></a>
for positive ideas, but the general trick is to leverage language semantics to manually cut the
compilation tasks into somewhat coarse-grained chunks which are independent by definition (of the
source language). Grug builds an incremental map-reduce compiler for his language:</p>
<ul>
<li>
<p>Recursive directory walk finds all files to be compiled.</p>
</li>
<li>
<p>In parallel, independently, each file is parsed, name-resolved, and lowered. As much as possible,
language features (and errors) are syntax driven and not type driven, and can be processed at this
stage.</p>
</li>
<li>
<p>In parallel, a “summary” is extracted from each file, which is essentially just a list of types
and signatures, with function bodies empty.</p>
</li>
<li>
<p>Sequentially, a “signature evaluation” phase is run on this set of summaries, which turns type
references in signatures into actual types, dealing with mutual dependencies between files. This
phase is re-run whenever a summary of a file changes. Conversely, changes to the body of any
function do not invalidate resolved signatures.</p>
</li>
<li>
<p>In parallel, every function’s body is type-checked, and lowered to type-and-layout resolved IR,
applying function-local optimizations.</p>
</li>
<li>
<p>Sequentially, a thin-lto style set of analyses are run on compiled functions, making inlining
decisions and computing call-graph dependent attributes like function purity.</p>
</li>
<li>
<p>In parallel, each function is codegened to machine code with unresolved references to other
functions (relocations).</p>
</li>
<li>
<p>Sequentially, functions are concatenated into an executable file, receiving an address.</p>
</li>
<li>
<p>In parallel, all relocations are resolved to now known addresses.</p>
</li>
</ul>
<p>The above scheme works only if the language has a property that changing the body of function <code>foo</code>
(not touching its signature) can’t introduce type errors into an unrelated function <code>bar</code>.</p>
<hr>
<p>Another trick that becomes less available if you blindly apply queries are in-place updates.
Consider a language with package declarations and fully qualified names, like Kotlin:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">package</span> org.example</span>
<span class="line"></span>
<span class="line"><span class="hl-function"><span class="hl-keyword">fun</span> <span class="hl-title">printMessage</span><span class="hl-params">()</span></span> { <span class="hl-comment">/*...*/</span> }</span>
<span class="line"><span class="hl-keyword">class</span> <span class="hl-title class_">Message</span> { <span class="hl-comment">/*...*/</span> }</span></code></pre>

</figure>
<p>A compiler for this language probably wants to maintain a map of all public declarations, where the
keys are fully qualified names, and values are declarations themselves. If you approach the problem
of computing this map with query eyes, you might have a base per-file query that returns a map of
file’s declarations, and then a recursive per-directory query. And you’ll probably have some kind of
structural sharing of the maps, such that changing a single file updates only the “spine”, without
actually copying most of the other entries.</p>
<p>But there’s a more direct way to make this sort of structure responsive to changes. You need only
two “queries” — per file, and global. When a file changes, you look at the <em>previous</em> version of
the map for this file, compute a diff of added or removed declarations, and then apply this diff to
the global map.</p>
<p>Zig is planning to use a similar approach to incrementalize linking — rather than producing a new
binary gluing mostly unchanged chunks of machine code, the idea is to in-place patch the previous
binary.</p>
<hr>
<p>If you like this article, you might be interested in some other adjacent stuff I’ve written over the
years, roughly in the order of importance:</p>
<ul>
<li>
<a href="https://rust-analyzer.github.io/blog/2020/07/20/three-architectures-for-responsive-ide.html">Three Architectures for a Responsive IDE</a>
</li>
<li>
<a href="https://rust-analyzer.github.io/blog/2023/07/24/durable-incrementality.html">Durable Incrementality</a>
</li>
<li>
<a href="https://matklad.github.io/2023/05/06/zig-language-server-and-cancellation.html">Zig Language Server And Cancellation</a>
</li>
<li>
<a href="https://matklad.github.io/2023/05/21/resilient-ll-parsing-tutorial.html">Resilient LL Parsing Tutorial</a>
</li>
<li>
<a href="https://rust-analyzer.github.io/blog/2019/11/13/find-usages.html">Find Usages</a>
</li>
<li>
<a href="https://rust-analyzer.github.io/blog/2023/12/26/the-heart-of-a-language-server.html">The Heart of a Language Server</a>
</li>
<li>
<a href="https://rust-analyzer.github.io/blog/2020/09/28/how-to-make-a-light-bulb.html">How to Make a 💡?</a>
</li>
<li>
<a href="https://matklad.github.io/2023/10/12/lsp-could-have-been-better.html">LSP could have been better</a>
</li>
<li>
<a href="https://matklad.github.io/2023/08/01/on-modularity-of-lexical-analysis.html">On Modularity of Lexical Analysis</a>
</li>
<li>
<a href="https://matklad.github.io/2020/11/11/yde.html">Why an IDE?</a>
</li>
</ul>
]]>
    </content>
  </entry>
</feed>
