<?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-07-22T00:47:24.175Z</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">Memory Safety&#039;s Hardest Problem</title>
    <link
      href="https://matklad.github.io/2026/07/20/memory-safety-hardest-problem.html"
      rel="alternate" type="text/html"
      title="Memory Safety&#039;s Hardest Problem" />
    <published>2026-07-20T00:00:00+00:00</published>
    <updated>2026-07-20T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/07/20/memory-safety-hardest-problem</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[Uplifting a lobsters comment for easier reference.]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/07/20/memory-safety-hardest-problem.html">
      <![CDATA[
<header>
  <h1>Memory Safety’s Hardest Problem</h1>
  <time class="meta" datetime="2026-07-20">Jul 20, 2026</time>
</header>
<p>Uplifting a
<a href="https://lobste.rs/s/vzkmtj/forget_borrow_checkers_c3_solved_memory#c_uuhbpy">lobsters comment</a>
for easier reference.</p>
<p>The central memory safety counter example, the hardest case to solve, doesn’t have anything to do
with destructors or heap:</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>
<span class="line"><span class="hl-keyword">const</span> E = <span class="hl-keyword">union</span>(<span class="hl-keyword">enum</span>) {</span>
<span class="line">    a: <span class="hl-type">u128</span>,</span>
<span class="line">    b: []<span class="hl-keyword">const</span> <span class="hl-type">u8</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"> main</span>() <span class="hl-type">void</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> bad_addr: <span class="hl-type">u128</span> = <span class="hl-built_in">@intFromPtr</span>(<span class="hl-operator">&amp;</span>main);</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">var</span> e: E = .{ .b = <span class="hl-string">&quot;hello&quot;</span> };</span>
<span class="line">    <span class="hl-keyword">const</span> oh_no_pointer: <span class="hl-operator">*</span><span class="hl-keyword">const</span> []<span class="hl-keyword">const</span> <span class="hl-type">u8</span> = <span class="hl-keyword">switch</span> (e) {</span>
<span class="line">        .a =&gt; <span class="hl-keyword">unreachable</span>,</span>
<span class="line">        .b =&gt; <span class="hl-operator">|</span><span class="hl-operator">*</span>p<span class="hl-operator">|</span> p,</span>
<span class="line">    };</span>
<span class="line">    e = .{ .a = (<span class="hl-numbers">16</span> <span class="hl-operator">&lt;&lt;</span> <span class="hl-numbers">64</span>) <span class="hl-operator">+</span> bad_addr };</span>
<span class="line">    <span class="hl-keyword">const</span> oh_no: []<span class="hl-keyword">const</span> <span class="hl-type">u8</span> = oh_no_pointer.<span class="hl-operator">*</span>;</span>
<span class="line">    std.debug.print(<span class="hl-string">&quot;{s}<span class="hl-string">\n</span>&quot;</span>, .{oh_no});</span>
<span class="line">}</span></code></pre>

</figure>

<figure class="code-block">


<pre><code><span class="line">$ zig run main.zig</span>
<span class="line">��C�� �</span></code></pre>

</figure>
<p>This sort of example also breaks Ada:</p>
<p><a href="https://www.enyo.de/fw/notes/ada-type-safety.html" class="url">https://www.enyo.de/fw/notes/ada-type-safety.html</a></p>
<p>We have a tagged union, which can hold either <code>A</code> or <code>B</code>. We initialize the union as
<code>A</code>, take a pointer to its internals, overwrite the original with <code>B</code>, and then use the pointer. The
pointer is still typed as <code>A</code>, but the bytes it points to now belong to <code>B</code>: a type confusion.</p>
<hr>
<p>This being said, we care about memory unsafety primarily because it leads to exploitable software,
and it’s unclear just how impactful the example above is in practice. It is a happy coincidence that
by far the most exploitable memory error in practice, the infamous buffer overflow, is also trivial
to fix with compiler-inserted bounds checks. The biggest miss of the industry when it comes to
memory safety is not listening to Walter Bright:</p>
<p><a href="https://digitalmars.com/articles/C-biggest-mistake.html" class="url">https://digitalmars.com/articles/C-biggest-mistake.html</a></p>
<p>I bet that, had we got <code>char a[..]</code> syntax around C11, quite a few issues wouldn’t have happened!</p>
<p>See also <a href="https://matklad.github.io/2025/12/30/memory-safety-is.html"><em>What is Memory Safety?</em></a></p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">CSS: Unavoidable Bad Parts</title>
    <link
      href="https://matklad.github.io/2026/06/04/css-unavoidable-bad-parts.html"
      rel="alternate" type="text/html" title="CSS: Unavoidable Bad Parts" />
    <published>2026-06-04T00:00:00+00:00</published>
    <updated>2026-06-04T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/06/04/css-unavoidable-bad-parts</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[An ersatz CSS tutorial for people who need to style a web page, but aren't web developers. I am a wrong person to write this kind of thing, as I have neither the time, nor experience. I'd much rather read a book about this. Alas, I had to learn all this stuff from trawling MDN, so perhaps it is valuable to document what I have so far.]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/06/04/css-unavoidable-bad-parts.html">
      <![CDATA[
<header>
  <h1>CSS: Unavoidable Bad Parts</h1>
  <time class="meta" datetime="2026-06-04">Jun 4, 2026</time>
</header>
<p>An ersatz CSS tutorial for people who need to style a web page, but aren’t web developers. I am a
wrong person to write this kind of thing, as I have neither the time, nor experience. I’d much
rather read a book about this. Alas, I had to learn all this stuff from trawling MDN, so perhaps
it is valuable to document what I have so far.</p>
<p>CSS, HTML and Web APIs are truly vast, and it takes a career to become a professional. The good news
is that modern web has a reasonably-sized, learnable subset which is enough for simple tasks like a
programming blog or a simple GUI. I haven’t seen a resource that teaches <em>just</em> this subset, but
it’s not too hard to figure this out. The bad news is that there’s also a nasty set of gotchas,
which will mess up your page, which you won’t suspect to exist, and which will need days of
debugging to figure out. Still, it’s not <em>that</em> bad. I am quite happy with the styling on this site,
and it’s only about <a href="https://matklad.github.io/css/main.css">200 of readable CSS</a>.</p>
<p><strong><strong>Good:</strong></strong> HTML5 semantic tag names<br>
It’s worth looking through MDN
<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements">Elements Reference</a>. There
aren’t that many elements, and things like <code>main</code>, <code>article</code>, <code>nav</code>, <code>kbd</code> make it much easier
to structure your page. Less obvious:</p>
<ul>
<li>
<code>ul</code> for any kind of list, like site’s sections in <code>header &gt; nav</code>.
</li>
<li>
<code>details</code> for table-of-contents (check the source of MDN).
</li>
<li>
<code>dl</code>/<code>dt</code> for list of pairs.
</li>
</ul>
<p><strong><strong>Bad:</strong></strong> Wrappers<br>
If you “View Source” on any “real” website, you’ll notice that everything has
layers and layers of wrapper elements, so you might be tricked into thinking that wrappers are how
you solve layout problems. I can’t really agree or disagree here, as I never wrote “production” CSS,
but, in my experience, it’s much easier to understand if you do the opposite — restrict yourself
to using only markup-meaningful semantic tags, and then figure out CSS which works with the markup
you have.</p>
<p><strong><strong>Bad:</strong></strong> Layout<br>
This one is not an exclusively Web problem, layout is a struggle in every GUI framework I know.
Imagine a fixed sized raster image, and a paragraph of text describing it. There are many ways to
arrange these two elements on the screen’s rectangle. Generally, for every given width and height,
you can do a decent job, as long as the total area is enough. A typical GUI is a hierarchy of such
boxes, with a lot of “layout freedom”. The problem though is that layout of each box affects the
layouts of all other boxes, as you generally want all boxes to meet exactly, without gaps and
overlaps. An important negative realization is that <em>the</em> layout algorithm doesn’t exist. There
isn’t a fully general solution to positioning and sizing GUI boxes. Rather, different systems use
different sets of heuristics to do the job, from simple
<a href="https://web.archive.org/web/20210306102303/https://halt.software/dead-simple-layouts/">RectCut</a>, to
fully general <a href="https://github.com/inamiy/Cassowary">constraint solvers</a>, with
<a href="https://www.youtube.com/watch?v=UUfXWzp0-DU">everything in between</a>.
It is hard to get the mental model of how layout works, <em>in general</em>. So, don’t think “how
can I do my layout in a given system”, think instead “what possible layouts are allowed by the
system”.</p>
<p><strong><strong>Bad:</strong></strong> Browser defaults<br>
Let’s start with a bare (but still semantic) HTML markup of a blog
article, without any CSS. If you open it in a browser, it will show <em>something</em>. The content isn’t
unstyled  — the text is of a certain color, font and size. Headers are bigger than the main text,
links are underlined, etc. These are the default styles of your browser. They are helpful! The
problem is that these styles differ between the browsers. So, even when you add your own CSS, and
the end result looks fine in your browser, I might see something different, because you might rely
on a browser default, without knowing it. The last bit is the killer here — the problem is in
something you <em>didn’t</em> write.</p>
<p>The general solution here is a <a href="https://www.joshwcomeau.com/css/custom-css-reset/">CSS reset</a>,
or normalization — starting your CSS with an explicit set of rules, overriding defaults. Not because defaults are inherently bad, because they are inconsistent. I don’t know <em>which</em> set of rules you need to override in practice, it’s a good idea to
compare several existing CSS resets.</p>
<p>This touches on the big question: <em>should</em> you style your web page? There are two competing views of
the Web platform — some people treat it as a flexible, adaptive, primarily visual medium for
expressing design, others would prefer if the Web focused on delivering the content, allowing each
user to customize the presentation. My personal answer here is pragmatic — by default, an unstyled
page is poorly usable and looks bad. I would have preferred the world where CSS-less pages were
readable as is, but, in this world, I think it is helpful to style the content. At the same time,
it’s a good idea to allow advanced users to bring their own CSS. Make sure that your HTML markup is
reasonable, that you don’t overfit your HTML to CSS (vice-versa is fine), and that your page
functions in reader mode.</p>
<p><strong><strong>Good:</strong></strong> <a href="https://boltcss.com">Classless CSS</a><br>
You can’t reset styles to true neutral nothing: if
you make the text invisible (white or transparent), it is still a style. So you might as well
embrace it: after reset, style common HTML elements directly. For example, to set your favorite font
for all code snippets:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-selector-tag">code</span> { <span class="hl-attribute">font-family</span>: <span class="hl-string">&quot;JetBrains Mono&quot;</span>, monospace; }</span></code></pre>

</figure>
<p>If you use <code>main</code>, <code>header</code>, <code>footer</code>, <code>nav</code> tags you can set the overall page layout without
writing any CSS selectors. This of course requires making assumptions, in CSS, about the structure
of your HTML, but, like, this is your HTML and your CSS, you can do whatever, and, if you don’t like
the result, you can always change it!</p>
<p><strong><strong>Bad:</strong></strong> CSS selectors<br>
In programming, we collectively came around to distrust inheritance and prefer composition. Default
CSS is like supercharged inheritance, each design element on your web page is affected by multiple
rules, and you can always “monkey patch” existing elements by appending to your CSS. There’s an
unfortunate gap between CSS affordances, and what you actually want to do. The two reasonable
approaches are:</p>
<ol type="A">
<li>
<p>Conclude that CSS selectors add abstraction capability along the wrong axis, and stick to
classless CSS and inline styles, using something like Tailwind to make writing inlines prettier, and
something like JSX (or any other templating engine supporting composition) to avoid repetition in
HTML.</p>
</li>
<li>
<p>Use <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Nesting">CSS nesting</a> to avoid
writing “far reaching” selectors and style component-per-component:</p>
</li>
</ol>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-selector-tag">header</span> { <span class="hl-comment">/* Site Header */</span></span>
<span class="line">    <span class="hl-attribute">margin-bottom</span>: <span class="hl-number">2rem</span>;</span>
<span class="line">    &amp; <span class="hl-selector-tag">nav</span> {</span>
<span class="line">        <span class="hl-comment">/* Styles, specific to nav in the Header. */</span></span>
<span class="line">    }</span>
<span class="line">}</span></code></pre>

</figure>
<p><strong><strong>Bad:</strong></strong> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/box-sizing">box-sizing</a><br>
UIs are recursive rectangles, layout is the process of figuring out where each rectangles
goes, and it is determined by the sizes of rectangles themselves. So, understanding <em>what</em> is the
size is quite fundamental. Sadly, by default the definition of size in HTML is very unintuitive:
element’s width and height do not include element’s border and padding, which leads to surprising
results: everything looks perfect at first, but increasing padding somewhere shifts the entire
layout unexpectedly. For this reason,
<span class="display"><code>* { box-sizing: border-box; }</code></span>
deserves to be the first line in your CSS reset. It makes elements encapsulated, such that adding
borders is a local-only change.</p>
<p><strong><strong>Chaotic Good:</strong></strong> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Box_model/Margin_collapsing">margin collapsing</a><br>
Suppose you want to have a <code>8px</code> gap around an element. You would <em>think</em> that you need to set the
padding property. But that would be wrong — if you have two such elements next to each other,
the gap between them would be <code>16px</code>. The paddings would add, creating a visual gap larger than
intended. You want something more akin to social distancing, where if one person is more
introverted, this person’s bigger radius of exclusion is what defines the distance. And that’s how
the <code>margin</code> property works. Two neighboring margins are combined using <code>max</code> rather than <code>sum</code>.
Margin collapsing is very useful, but it can surprise you. E.g. I <em>think</em> child margin can
stick beyond parent’s? To be honest, I don’t have a good intuitive understanding of margins, but I
know enough to at least identify when it is the problem.</p>
<p>Margins are also one of the indirect inspirations for this post. In</p>
<p><span class="display"><a href="https://jvns.ca/blog/2026/05/15/moving-away-from-tailwind--and-learning-to-structure-my-css-/"><em>Moving away from Tailwind, and learning to structure my CSS</em></a></span></p>
<p>Julia Evans writes that you generally don’t want to set margin on an element, and should rather let
the parent control the inter-element margin of the children, using the so-called owl selector:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-selector-tag">section</span> &gt; *+* {</span>
<span class="line">  <span class="hl-attribute">margin-top</span>: <span class="hl-number">1rem</span>;</span>
<span class="line">}</span></code></pre>

</figure>
<p>That is, add margin to all <code>section</code>’s children exempting the first one. I didn’t know that! And,
given all the pain that margin gave me so far, I actually get why you want to do this, and why this
is a good idea. But it bugs me that you can’t learn that without becoming “professional” web
developer, or reverse-engineering someone else’s CSS framework.</p>
<p><strong><strong>Bad:</strong></strong> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Display/Block_and_inline_layout">Default (flow)
layout</a><br>
Layout in general is tricky, because there’s no universal “layout
algorithm”, just a bunch of special cases. But what does HTML actually do? The default layout
algorithm I <em>think</em> goes back to the origin of HTML as a language for documents, and overfits a
use-case of producing papers — mostly text content with some illustrations, where the text can
flow around the pictures. That’s actually what you want for the main body of text of your blog, but,
as soon as you want to actually control the spatial arrangement of the elements on your page, you
want something different, for example…</p>
<p><strong><strong>Good:</strong></strong> <a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/CSS_layout/Flexbox">flexbox</a><br>
This is really what separates modern web-development from the olden days, where you’d need a CSS PhD or
a full-blown opaque CSS framework to be able to say “this goes to the left, and this goes to the
right”. This layout allows you to arrange a series of elements either vertically or horizontally,
adapting to the available space. It is rather complex and I can’t use flexbox without referencing
MDN all the time, but usually I am able to get things done in the end.</p>
<p><strong><strong>Bad:</strong></strong> responsive design<br>
Modern CSS allows querying screen size, and implementing conditional logic based on that — a
design that “responds” to user-agent constraints. This probably what you should use for “real” CSS,
but note that HTML is <em>inherently</em> responsive. Unlike PostScript (PDF), it will automatically reflow
the paragraphs when you change window size. So, it’s a good idea to avoid writing <em>explicit</em>
responsive rules, and just rely on layout to do the reasonable thing. For example, this blog looks
OK on mobile, tablet and desktop without any explicit <code>@media</code> queries. Unconditionally setting <code>max-width </code> on the main column of text is all that it takes.</p>
<p><strong><strong>Lawful Evil:</strong></strong> pixels<br>
<code>1px</code> does what you want, but not what it says. It’s <em>not</em> a size of one physical
pixel on your screen. Rather, it’s a measure of <a href="http://inamidst.com/stuff/notes/csspx">visual
angle</a>. That is, <code>1px</code> should look perceptually the same on
any screen, and it is converted to different number of physical pixels, depending on the screen size, its pixel density, and the typical viewing distance. So you <em>can</em> just size everything in pixels, without thinking about different
displays’ pixel densities. It gets weirder. CSS allows “real” units like centimeters or inches, but
they are <em>also</em> angles, because everything is <em>defined</em> in terms of pixels.</p>
<p><strong><strong>Doubleplusungood:</strong></strong> <a href="https://tonsky.me/blog/font-size/">font-size</a><br>
Flexbox is a good way to layout UI-elements. Flow layout works ok for laying out paragraphs of text.
But what happens on the level of individual lines and glyphs is, in my opinion, a train wreck and a
noob trap. Let’s start with the basics: if you write
<span class="display"><code>font-size: 16px</code></span>
then <code>16px</code> is the size of what? Sadly, the answer is “nothing in particular” — this is a size of
a virtual box around the glyph, but the box isn’t tight, and the size of the glyph varies, depending
on the font. Luckily, <code>font-size-adjust</code> property can fix it, and make <code>font-size</code> consistent across
fonts. See these two posts for details:</p>
<ul>
<li>
<a href="https://matklad.github.io/2025/07/16/font-size-adjust.html">font-size-adjust Is Useful</a>
</li>
<li>
<a href="https://tonsky.me/blog/font-size/">Font size is useless; let’s fix it</a>
</li>
</ul>
<p>Though, at the moment <code>font-size-adjust</code> seems to be very niche, so, while personally I’d put
<span class="display"><code>font-size-adjust: ex-height 0.53;</code></span>
right next to <code>box-sizing</code>, few pages do that.</p>
<p>The next issue with <code>font-size</code> is a thorny question of defaults. The good news is that it’s one of
the properties that is fairly consistent across browsers, with <code>16px</code> being the overwhelming
default. The bad news is that, depending on the font, <code>16px</code> can be on the smaller size. Not
completely illegible, but very close to the lower bound. What’s worse, some <em>default</em> fonts are
particularly small. For example, on Apple,
<span class="display"><code>font-family: serif</code></span>
looks much smaller than <code>sans-serif</code>, and is almost uncomfortable to read at 16px.</p>
<p>Can you just set
<span class="display"><code>font-size: 18px</code></span>
or whatever works best for your chosen font? I think the answer is yes, but there are some caveats to
keep in mind. Refer to
<span class="display"><a href="https://matklad.github.io/2022/11/05/accessibility-px-or-rem.html"><em>Accessibility: px or rem?</em></a></span>
for details. The issue is that modern browsers support two ways of making text on a page bigger:</p>
<ul>
<li>
Zoom, which has a dedicated UI element, shortcuts/gestures, per-page persistence/overrides
and a global default.
</li>
<li>
Changing default font-size, a global setting buried deeply in the configuration page.
</li>
</ul>
<p>Setting <code>font-size</code> in your CSS disables that second approach.</p>
<p>Taking everything together: don’t assume that text on your page will be readable by default, check
different configurations. Set <code>font-size-adjust</code> to reduce the number of degrees of freedom and to
pin down the meaning of <code>font-size</code>. If the result looks fine with your chosen (or your user’s
default) font and default font-size of <code>16px</code>, then you are done. Otherwise, set <code>font-size</code> to a
bigger number. Afterwards, check that the page is readable in reader mode as well.</p>
<p><strong><strong>Bad:</strong></strong> <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/line-height"><code>line-height</code></a><br>
Despite the name, <code>line-height</code> doesn’t set the height of a line. It is a height of a run of glyphs,
<em>set in the same font</em>. The two coincide when all the text is in the same font. But if you have,
e.g., some words set in <code>monospace</code> font, you are in for a surprise. While <code>font-size-adjust</code> fixes
the size of a glyph inside the box, it still leaves its relative position unspecified. So, when two
runs of text in different fonts are aligned vertically to share the baseline, their line-height
line-boxes get shifted relative to each other: one sticks below, one sticks above. The line height
overall becomes larger that what you’d expect, as it is configured as a union. See</p>
<p><a href="https://iamvdo.me/en/blog/css-font-metrics-line-height-and-vertical-align" class="display">Deep dive CSS: font metrics, line-height and vertical-align</a></p>
<p>for a thorough explanation of this effect.</p>
<p><strong><strong>Bad:</strong></strong> vertical rhythm<br>
If you google long enough this cluster of problems, sooner or later you’ll come across the idea of
vertical rhythm, that you should make sure that lines are in the same relative position across
different paragraphs, even if you have headings, images, and what not. As if there’s invisible
lined paper behind your web-page. As far as I can tell, this is pure voodoo and is not useful. <em>If</em>
you do two-column layout, then you want lines on opposite sides to align, but it makes no sense to
jump through hoops for a single-column layout (hat tip to <a href="https://lobste.rs/s/noahb3/readable_css#c_pre4ii">@chrismorgan</a>).</p>
<p><strong><strong>Bad:</strong></strong> <code>word-break</code><br>
The genius of the flow layout is its dynamism. It takes a moment of reflection to appreciate the
technical marvel of text breaking itself neatly into lines as the window is resized to be narrower.
Getting that to work for the first time ever in the world of durably printed text must have felt
incredible. But the magic has its limits — you can only break the line at the whitespace, or at
the hyphenation points. And some long spans, like <code>inline code</code> or URLs, might be unbreakable. This
leads to overflow annoyance on mobile devices, something you notice only <em>after</em> you publish your
work. There’s no one trick to fix it, but some tips are available here:
<a href="https://matklad.github.io/2025/04/22/horizontal-scroll.html" class="display"><em>Against Horizontal Scroll</em></a>
for details.</p>
<hr>
<p>And … that’s all I remember so far? I reiterate my request for someone to write a short 100-page
book explaining just enough of HTML&amp;CSS to make a simple blog without getting collapsed by the
margins!</p>
]]>
    </content>
  </entry>
  <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 infallible 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-Running">

<h2><a href="#Stepping-And-Running">Stepping And Running</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>
</feed>
