<?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-09-15T00:52:14.449Z</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">Static Allocation, Constant Work</title>
    <link
      href="https://matklad.github.io/2026/09/02/static-allocation-constant-work.html"
      rel="alternate" type="text/html"
      title="Static Allocation, Constant Work" />
    <published>2026-09-02T00:00:00+00:00</published>
    <updated>2026-09-02T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/09/02/static-allocation-constant-work</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[In reply to this email:]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/09/02/static-allocation-constant-work.html">
      <![CDATA[
<header>
  <h1>Static Allocation, Constant Work</h1>
  <time class="meta" datetime="2026-09-02">Sep 2, 2026</time>
</header>
<p>In reply to this email:</p>

<figure class="blockquote">
<blockquote><p><a href="https://matklad.github.io/2026/07/20/memory-safety-hardest-problem.html"><em>Memory Safety’s Hardest Problem</em></a>
named something I’d hit but couldn’t articulate.
Your case is a pointer into one union variant surviving a write of a different
variant, so live typed pointers end up reading bytes that belong to something
else now.</p>
<p>Last year I wrote a limit-order matching engine and shipped a use-after-free: a
cancelled order was released back to the pool while it was still linked into its
price level, so the next allocation handed that memory to a new order and the
stale link kept resolving. I’d filed it under “I was careless with lifetimes.”</p>
<p>After your post I’m not sure that’s what it was. A recycling pool looks like a
tagged union where the tag is “which generation of object currently lives in
this slot,” and nothing in the type system tracks it. Is that a fair reading, or
does the pool case stay genuinely easier because generational indices actually
solve it and the union case has no equivalent?</p>
</blockquote>

</figure>
<p>Yes, object pools are an interesting case to think about, as they clarify the
relation between <em>memory</em> safety and more general correctness.</p>
<p><em>First</em>, consider the case where no object pool is used, and we <code>malloc</code> and
<code>free</code> order objects. In this case, the logical error of use-after-free turns
into physical type confusion, and can easily lead to arbitrary code execution
and the like. If you have two objects of different <em>types</em> sharing the same
memory location, a user-controlled integer in one object might be a function
pointer in the other: an exploitable <code>goto</code> primitive</p>
<p>Now, what happens if we introduce an object pool which stores a list of “dead”
objects of type <code>T</code>? Logical use-after-free is still possible, but its physical
effect is now different — we still get aliasing of memory, but there’s no type
confusion. You can’t necessarily fiddle with an integer and change a function
pointer, unless you additionally hit the hard case, where the object in question
stores an inline enum. Assuming that doesn’t hapen, you get a perfectly defined,
deterministic behavior, even if you are not happy about the result.</p>
<p>This suggests an interesting solution for hardening code, which I’ve learned
from <a href="https://fil-c.org/meet_fil">Fil</a>. <em>If</em> your allocation function is typed
(it takes a <code>T</code> comptime parameter or runtime type witness, rather than a
runtime type-erased size and alignment), you can write an allocator that uses
type-segregated pools internally. This will be somewhat less memory efficient,
as the allocator won’t be able to re-use freed memory of objects of type <code>U</code> for
objects of type <code>T</code>, but the memory overhead will probably be small (rare object
types do not matter, popular object types will have a lot of intra-type re-use),
you might actually gain in memory locality, <em>and</em> solve most of type confusions.
Again, inline enums break this, but, curiously, if you always heap allocate enum
variants, then this works again. Fil-C can’t use this, because C allocator’s
interface is untyped, but someone else could :P</p>
<p>But this is academic. How do we avoid the bugs? Generational indexes are a
popular remedy, but I have never used them, so I don’t have any non-common
knowledge insights about this pattern. Instead, I will share another pair of tricks
from <a href="https://github.com/tigerbeetle/tigerbeetle/blob/main/docs/TIGER_STYLE.md">TigerStyle</a>.
I have only a vague understanding of what an order matching engine is, but I
suspect these tricks might help there</p>
<section id="Static-Allocation">

<h2><a href="#Static-Allocation">Static Allocation</a></h2>
<p>The first one is:</p>

<figure class="blockquote">
<blockquote><p>No dynamic memory allocation after initialization</p>
</blockquote>

</figure>
<p><a href="https://www.youtube.com/watch?v=GRJtYwneG2Q&amp;t=1823s" class="display url">https://www.youtube.com/watch?v=GRJtYwneG2Q&amp;t=1823s</a></p>
<p>This is the pool idea, taken to its logical conclusion. We specify the maximum
number of orders we are willing to work with at startup, and never go beyond
that. So, you might start the program as</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-title function_">$</span> order-engine --orders-max=1_000_000</span></code></pre>

</figure>
<p>and then one of the first lines in its <code>main</code> function would be :</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> orders: []Order = <span class="hl-keyword">try</span> gpa.alloc(Order, cli_args.orders_max);</span></code></pre>

</figure>
<p>If, at runtime, more than <code>orders_max</code> requests come in, the surplus requests
are rejected. Someone might object: “But what if I actually have some spare
memory for one more order? Wouldn’t it be a good idea to at least try to handle
it?”</p>
<p>My rejoinder would be “Well, what if you don’t?”. Systems operating at capacity
<em>without</em> strict limits fail catastrophically. Attempting to allocate just one
more <code>Order</code> could cause kernel’s OOM killer to terminate the entire order
matching engine, losing the other million orders, or, better yet, to kill the
supervisor process so that you can’t even restart.</p>
<p>Static allocation gives you peace of mind. The system might fail to start if you
don’t have enough memory, but, if it did start, you can be rest assured that it
would handle overload gracefully, continuing to render the service while you are
provisioning a beefier machine.</p>
</section>
<section id="Constant-Work">

<h2><a href="#Constant-Work">Constant Work</a></h2>
<p>What would you do with the slice of orders? One approach is to
<code class="display">@memset(orders, undefined)</code>
and hand the slice over to a pool which tracks spare objects with a bit set:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> OrderPool = <span class="hl-keyword">struct</span> {</span>
<span class="line">    orders: []Order,</span>
<span class="line">    free: DynamicBitSet,</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">fn</span><span class="hl-function"> acquire</span>(pool: <span class="hl-operator">*</span>OrderPool) ?<span class="hl-operator">*</span>Order { ... }</span>
<span class="line">    <span class="hl-keyword">fn</span><span class="hl-function"> release</span>(pool: <span class="hl-operator">*</span>OrderPool, order: <span class="hl-operator">*</span>Order) { ... }</span>
<span class="line">};</span></code></pre>

</figure>
<p>or with a free list:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> OrderPool = <span class="hl-keyword">struct</span> {</span>
<span class="line">    orders: []<span class="hl-keyword">union</span> {</span>
<span class="line">        order: Order,</span>
<span class="line">        next_free: ?<span class="hl-type">u32</span>,</span>
<span class="line">    },</span>
<span class="line">    first_free: ?<span class="hl-type">u32</span>,</span>
<span class="line">};</span></code></pre>

</figure>
<p>But there’s an alterative approach. Instead of thinking about a limit on the
number of orders, you could instead design the system to <em>always</em> have a fixed
amount of orders, by introducing a no-op, neutral order:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> Order = {</span>
<span class="line">    id: <span class="hl-type">u128</span>,</span>
<span class="line">    price: <span class="hl-type">u32</span>,</span>
<span class="line">    count: <span class="hl-type">u32</span>,</span>
<span class="line"></span>
<span class="line">    tag: <span class="hl-keyword">enum</span> { bid, ask, reserved },</span>
<span class="line"></span>
<span class="line">    <span class="hl-keyword">pub</span> <span class="hl-keyword">const</span> reserved: Order = .{</span>
<span class="line">        .id = <span class="hl-numbers">0</span>,</span>
<span class="line">        .price = <span class="hl-numbers">0</span>,</span>
<span class="line">        .count = <span class="hl-numbers">0</span>,</span>
<span class="line">        .tag = .reserved,</span>
<span class="line">    };</span>
<span class="line">};</span></code></pre>

</figure>
<p>Your initialization then becomes <span class="display"><code>@memset(orders, .reserved)</code>.</span></p>
<p>One benefit here is cognitive, you no longer think in terms of creating and
destroying orders. Instead, the orders merely circulate in the system according
to the law of the conservation of the number of orders. It becomes harder to
loose track of an order if you must always pay attention not only to where the
order goes, but also to where it came from. You explicitly write state
transition functions for each <em>pair</em> of states, and that makes it easier to
exhaustively enumerate all the cases. And you double check that with asserting,
at every point, that the state is what you expect it to be (and then you DST the
asserts) .</p>
<p>Another benefit is code simplification and predictability. You no longer need to
track a separate collection of “live” orders. Instead, you always iterate the
full set, doing no-ops for reserved. This feels wasteful: should we make the
code run faster when there are few orders? But consider this: by specifying the
limit of orders up-front, you commit to be able to serve that amount. <em>If</em> the
maximum amount of orders is active, does the system have acceptable performance?
If not, that is a bug! Gray failure (system becoming unusably slow) is another
way to break when reaching the limit.</p>
<p>Avoiding indexes improves performance for the maximal load case. This</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">for</span> (orders) <span class="hl-operator">|</span>order<span class="hl-operator">|</span> {</span>
<span class="line">    process(order)</span>
<span class="line">}</span></code></pre>

</figure>
<p>is much easier for compiler to vectorize, and for CPU Cache to prefetch, than
this:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">for</span> (orders_active) <span class="hl-operator">|</span>order_index<span class="hl-operator">|</span> {</span>
<span class="line">    <span class="hl-keyword">const</span> order = orders[order_index];</span>
<span class="line">    process(order);</span>
<span class="line">}</span></code></pre>

</figure>
<p>Similarly to static allocation, the <em>Constant Work</em> principle gives you peace of
mind with respect to performance. P100 latency stays flat regardless of the
load. Insufficient performance is discovered when you roll out the system, not
during Black Friday on-call.</p>
<p>At TigerBeetle, we apply this pattern in the small. Rather than writing a
search loop with an early return:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> item = <span class="hl-keyword">for</span> (items) <span class="hl-operator">|</span>item<span class="hl-operator">|</span> {</span>
<span class="line">    <span class="hl-keyword">if</span> (predicate(item)) <span class="hl-keyword">break</span> item;</span>
<span class="line">} <span class="hl-keyword">else</span> <span class="hl-literal">null</span>;</span></code></pre>

</figure>
<p>we sometimes let the loop to run its full natural course, additionally asserting
that theres a <em>unique</em> matching item:</p>
<p><a href="https://github.com/tigerbeetle/tigerbeetle/blob/0.17.9/src/vsr/grid.zig#L715-L725" class="url">https://github.com/tigerbeetle/tigerbeetle/blob/0.17.9/src/vsr/grid.zig#L715-L725</a></p>
<hr>
<p>As usual, this is <em>a</em> trick which is useful to have in your arsenal, but it
isn’t a universal solution to all programming’s problems.</p>
</section>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Cancelation Terminology</title>
    <link
      href="https://matklad.github.io/2026/08/31/cancelation-terminology.html"
      rel="alternate" type="text/html" title="Cancelation Terminology" />
    <published>2026-08-31T00:00:00+00:00</published>
    <updated>2026-08-31T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/08/31/cancelation-terminology</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[A short note explaining the difference between synchronous cancelation, asynchronous cancelation, and graceful shutdown. I am not too attached to these specific three terms, but I want to call your attention to the three things behind them, which are important not to confuse with each other.]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/08/31/cancelation-terminology.html">
      <![CDATA[
<header>
  <h1>Cancelation Terminology</h1>
  <time class="meta" datetime="2026-08-31">Aug 31, 2026</time>
</header>
<p>A short note explaining the difference between synchronous cancelation,
asynchronous cancelation, and graceful shutdown. I am not too attached to these
specific three <em>terms</em>, but I want to call your attention to the three
<em>things</em> behind them, which are important not to confuse with each other.</p>
<p><dfn>synchronous cancelation</dfn> is an (often implicit) control flow structure.
It unwinds the stack and looks like this:</p>

<figure class="code-block">


<pre><code><span class="line">task.<span class="hl-title function_ invoke__">cancel</span>();</span>
<span class="line"><span class="hl-comment">// The task will have finished by this point.</span></span></code></pre>

</figure>
<p>Synchronous cancelation is a bit like Molière’s prose — we do it all the time,
but not necessarily in full consciousness. The primary source of synchronous
cancelation is error handling — every time an <code>Exception</code> is thrown or an
<code>error</code> returned, the code promptly breaks out of all the loops, ifs, and
blocks, invoking the necessary cleanup actions via RAII, <code>finally</code>, <code>with</code>/<code>try</code>
with resources or <code>defer</code>.</p>
<p><dfn>asynchronous cancelation</dfn> is a communication protocol between two
parties. One party requests cancelation (synchronously), but then it has to wait
until the other party acknowledges it and winds down. It looks like this:</p>

<figure class="code-block">


<pre><code><span class="line">task.<span class="hl-title function_ invoke__">request_cancelation</span>();</span>
<span class="line"><span class="hl-comment">// The task could still be running here.</span></span>
<span class="line">task.<span class="hl-title function_ invoke__">join</span>().<span class="hl-keyword">await</span>;</span>
<span class="line"><span class="hl-comment">// After the requisite wait, the task is finished.</span></span></code></pre>

</figure>
<p>Like synchronous cancelation, this is a relatively low-level concern when
implementing a concurrent program in a way that doesn’t crash or hang. I know
two central example where an asynchronous cancelation is required.</p>
<p>First is the CPU thread pool. Imagine you have offloaded encrypting a buffer to
a separate thread as a part of handling user’s request. Some time later, you
learn that the request must be canceled (perhaps the user had left). You can’t
just abandon the encrypting thread. First, it would be smart not to waste CPU
cycles for useless work, but, more importantly, the underlying <em>buffer</em> must
remain tied up. If it were to be freed as a result of request cancelation,
something else might re-use that memory, leading to data races.</p>
<p>But you also can’t just cancel that thread synchronously! It’s in the middle of a
hyper-optimized SIMD loop, and you really don’t want it to check the cancelation
flag before reading every byte. What you’d want is to split the buffer into
reasonably-sized chunks, and check the cancelation status after every chunk. But
that means that the party that requested the cancelation must wait for at least
one chunk’s worth of work!</p>

<aside class="admn note">
<svg class="icon"><use href="/assets/icons.svg#info"/></svg>
<div><p>For the curious, there’s actually a bit more leeway with canceling CPU work
non-cooperatively, see</p>
<p><a href="https://go.dev/src/runtime/preempt.go" class="display url">https://go.dev/src/runtime/preempt.go</a></p>
</div>
</aside><p>Another example here is <code>io_uring</code>. It has exactly the same shape: if you submit
a write with a buffer to the kernel, that buffer must remain tied up until the
write finishes (and you can cancel the write to make it finish faster). While
<code>io_uring</code> is still at least a somewhat exotic technology (though, arguably,
it’s the interfaces we have had before which are byzantine), the thread pool
example demonstrates that the phenomenon of asynchronous cancelation itself is
rather mundane.</p>
<p>Asynchronous cancelation comes up all the time when writing concurrent software.
Because it affects the overall shape of the code, it’s useful to identify it
early. Conversely, it is useful to ask yourself whether you need asynchronous
cancelation at all, and whether synchronous one can be made to work. This is
especially important in Rust, which makes synchronous cancelation too easy, and
doesn’t provide great mechanisms for asynchronous one.</p>
<p>Finally, <dfn>graceful shutdown</dfn> is an application programming pattern for
handling connections. It lives on a higher level of abstraction than the two
cancelations. If you are implementing a web service, you can implement shutdown
by stopping your <code>accept</code> loop (rejecting new connections), but continuing to
serve all existing connections until their respective clients disconnect. If the
load balancer is configured to route new connection requests to different
instances of the service, this pattern allows you to do rolling upgrades without
service disruptions.</p>
<p>As a bonus point, a related idea is that of
<a href="https://www.usenix.org/legacy/events/hotos03/tech/full_papers/candea/candea_html/index.html">crash-only software</a>.
Cancelation is all good, but your entire program can get SIGKILLed arbitrarily
by an OOM killer, and the entire computer might get rebooted on powerloss.
Reliable software has to handle ungraceful shutdown without losing data. But, if
you can survive powerloss, you might as well implement the <kbd><kbd>Quit</kbd></kbd> button
by SIGKILLing yourself, simultaneously simplifying the implementation and
increasing testing coverage for powerloss scenarios.</p>
<hr>
<p>To give some examples from TigerBeetle,
<a href="https://github.com/tigerbeetle/tigerbeetle/blob/47aeb2212a255273dda508288412e537d11e4b7c/src/vsr/grid.zig#L589"><code>Grid.cancel</code></a>
is an asynchronous cancelation. It takes a callback to notify the caller when
the cancelation is done. This API is used during state sync. When a replica
determines that that cluster is so far ahead that event based transfer doesn’t
work, and that a state transfer is required to catch up, it must cancel all
outstanding grid read operations. A read can be backed either by replica’s local
disk, or by transparent fetch of the data from a neighboring replica. In the
first case, we have to wait until the read is done. In the second case, we need
to abandon the read — remote read getting stuck is probably <em>the</em> reason for
us to state sync in the first place.</p>
<p><a href="https://github.com/tigerbeetle/tigerbeetle/blob/47aeb2212a255273dda508288412e537d11e4b7c/src/state_machine.zig#L942"><code>StateMachine.reset</code></a>
is an example of a synchronous cancelation. This is the part of the same flow as
<code>Grid.cancel</code>, and is an example of how you can simplify the code if you think
clearly about asynchronous vs synchronous cancelation. Ultimately,
<code>StateMachine</code> sits on top of the <code>Grid</code>, but there’s a bunch of intermediate
layers (<code>Forest</code>, <code>Tree</code>, <code>Compaction</code>, <code>Scan</code>, etc). A naive approach would be
to notice that <code>Grid</code> requires asynchronous cancelation and propagate asynchrony
throughout the stack. What we do instead is asynchronously canceling <em>just</em> the
<code>Grid</code> directly, and then synchronously <code>reset</code>ing everything else.</p>
<p>Another example of asynchronous cancelation is
<a href="https://github.com/tigerbeetle/tigerbeetle/blob/47aeb2212a255273dda508288412e537d11e4b7c/src/vsr/client.zig#L194-L203"><code>Client.shutdown</code></a>.
When an application using TigerBeetle “drops” the <code>Client</code> object, we need to
free all OS resources. Our client also uses io_uring, so we must first wait for
all outstanding syscalls to complete. In the comment, we call it “graceful
shutdown”, but I think this is wrong, and this is the motivation for writing
down this article. We don’t do graceful shutdown at TigerBeetle — it’s crash
only all the way. Tail latency tolerance (asking several nodes for an answer and
picking the fastest one) is a more general solution, as it handles not only
crash faults, but also
<a href="https://www.microsoft.com/en-us/research/wp-content/uploads/2017/06/paper-1.pdf">gray failures</a>.
In a distributed system, a very slow node looks exactly the same as a crashed
one. A crash is just a degree of slowness.</p>
<hr>
<p>Take aways:</p>
<ul>
<li>
Synchronous cancelation is control flow operator
</li>
<li>
Asynchronous cancelation is a communication protocol
</li>
<li>
Graceful shutdown is an application-level design pattern
</li>
</ul>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Rust Glancer</title>
    <link href="https://matklad.github.io/2026/08/21/rust-glancer.html"
      rel="alternate" type="text/html" title="Rust Glancer" />
    <published>2026-08-21T00:00:00+00:00</published>
    <updated>2026-08-21T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/08/21/rust-glancer</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[Rust Glancer, a functional LSP server for Rust which uses two orders of magnitude less RAM, is incredibly cool. Go check it out! This post started as a comment on lobste.rs, but I figured it out that it's better to publish it somewhat more prominently. Don't expect polished writing though!]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/08/21/rust-glancer.html">
      <![CDATA[
<header>
  <h1>Rust Glancer</h1>
  <time class="meta" datetime="2026-08-21">Aug 21, 2026</time>
</header>
<p><a href="https://rust-glancer.github.io/blog/hello-world/">Rust Glancer</a>, a functional
LSP server for Rust which uses two orders of magnitude less RAM, is incredibly
cool. Go check it out! This post started as a comment on lobste.rs, but I
figured it out that it’s better to publish it somewhat more prominently. Don’t
expect polished writing though!</p>
<p>Some thoughts:</p>

<figure class="blockquote">
<blockquote><p>rust-analyzer uses rowan for syntax tree representation</p>
</blockquote>

</figure>
<p>Yeah, rowan is garbage :P I was really thinking about</p>
<ul>
<li>
incremental parsing,
</li>
<li>
incremental, DOM-mutation style refactorings,
</li>
</ul>
<p>And Rowan is pretty good for that. But that’s 1% use case. The 99% use case is
all the code in your 6666 dependencies which you won’t ever look at, but which
needs to be at least shallowly analyzed. Even for incremental tool whose main
goal is refactoring, the primary AST structure should be just a list of arrays.
There might be a real post about that at some point, see
<a href="https://youtu.be/G93oYL1ry70" class="url">https://youtu.be/G93oYL1ry70</a> as a teaser.</p>

<figure class="blockquote">
<blockquote><p>Rust workspaces genuinely have a lot of information that must be indexed:
thousands of functions, structures, traits, relationships between these,
function bodies and statements in them, etc. Each of these needs to be
analyzed and remembered, and you can’t really cheat if you want to have things
like “find all references to this structure”.</p>
</blockquote>

</figure>
<p>If I understand correctly, Rust Glancer wants to process each function body. I
think <em>that</em> part can perhaps be made lazy (but not incremental!) with little
overhead? Index all items, but, for functions, do only the currently opened
file? This might combine some of the better parts of both worlds.</p>
<p>Would be interesting to compare memory usage with Rust Rover. Net of the IDE
GUI itself, I would expect RR to be more compact.</p>

<figure class="blockquote">
<blockquote><p>Some features are unlikely to be supported though, such as build scripts /
proc macros support via proc macro invocation</p>
</blockquote>

</figure>
<p>I might be rationalizing/misremembering things, but IIRC it’s exactly around
adding proc macros that the thing began to feel unreasonably bulky. Expanding
proc macros is slow as we are running real code, we can’t really do normal IDE
cheats. And proc macros generate a lot of code. At one point I measured, it was
like 30% of rust-analyzer binary size was attributed to JSON parsing code. If no
one sees the code, it can’t harm anybody, right?</p>
<p>One potential approach here is to pull the Sorbet trick, where you don’t run
meta programming at all, and instead have a plugin interface to “explain” the
<em>effects</em> of what that would have done. Instead of running serde, we just add a
shim that injects <code class="display">imp Serialize for T {}</code> with an empty body.</p>

<figure class="blockquote">
<blockquote><p>I’m not sure why, but in rust-analyzer I’ve observed that when agents edit the
code, inlay hints can get out of place</p>
</blockquote>

</figure>
<p>Rust analyzer’s core data model is <em>very</em> pedantic about always observing
consistent snapshots of the code, and does its best to ensure that the language
client and server have a shared, strictly serializable view of the world. It’s a
shame that
<a href="https://github.com/Microsoft/language-server-protocol/issues/584">LSP doesn’t allow that to be <em>correct</em>, only heuristically right</a>,
unlike the older Dart Analyzer protocol, which has sound data synchronization.</p>
<p>However our implementation of file watching is sketchy! First, there are two
backends: we can ask the editor to do watching for us, or we can use server side
watching. Try changing
<a href="https://rust-analyzer.github.io/book/configuration.html#files.watcher">this option</a>
and see if it helps? But then, yeah, my recollection is that our native watcher’s API was
fundamentally racy, and I didn’t do the messy platform-specific work of making it correct.</p>
<hr>
<p>But the main thing I want to write, and why I moved from the cozy lobste.rs text
area to the luxurious comforts of an Emacs buffer, is that right now rust-analyzer is a bit
like that half-drawn horse meme, except that it’s only the head half of the
horse.</p>
<p>One Big Idea of IntelliJ is that its PSI API (essentially AST with resolved
types) is really an interface, and there are multiple providers. And in a typical
usage, there’re at least three backends in play:</p>
<ul>
<li>
For the files opened in the editor, actively modified by the user, the PSI is
backed by the concrete syntax trees.
</li>
<li>
For the rest of the project files, the PSI is backed by the so called Stub
Tree, a compact on disk representation storing only the “externally visible”
parts of the file (so, without function bodies). If the user navigates to a
new file, its PSI transparently switches from stubs to syntax tree.
</li>
<li>
For dependencies, the PSI is often backed by the compiled .class files,
produced by javac. If you navigate there, the IDE just decompiles stuff for
you! Super cool!
</li>
</ul>
<p><em>This</em> is how I think such things should work. rust analyzer <em>shouldn’t</em> use
salsa for all those 6666 dependencies you still haven’t looked at. It should
just use rustc’s .rmeta files, switching to salsa, transparently, only when the
user starts messing around their <code class="display">~/.cargo/registry/src</code> folder.</p>
<p>The prerequisite for that is defining the abstract API for accessing Rust code.
That was always the plan, and we did start on that at some point:</p>
<p><a href="https://hackmd.io/ytd82QNiT_Ku2XFr1EAtiQ" class="url">https://hackmd.io/ytd82QNiT_Ku2XFr1EAtiQ</a></p>

<figure class="blockquote">
<blockquote><p>rmeta-transparent – source code might not be available for some crates, the API
should support pre-compiled rmeta files as inputs.</p>
</blockquote>

</figure>
<p>But I don’t think that work was ever completed.</p>
<p>This still seems to me to be the lowest-hanging watermelon here — split the
world into arcy-pointy incremental tip of the iceberg, and mostly read-only,
on disk, compact, dark, moist breeding ground for supply chain attacks.</p>
<p>Such glance analyzer architecture would be great, imo!</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Better Batteries</title>
    <link href="https://matklad.github.io/2026/08/20/better-batteries.html"
      rel="alternate" type="text/html" title="Better Batteries" />
    <published>2026-08-20T00:00:00+00:00</published>
    <updated>2026-08-20T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/08/20/better-batteries</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[One of the eternal schisms in programming is over the question of whether the standard library should be minimal or encompassing. This is the wrong question to ask. The right one is:]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/08/20/better-batteries.html">
      <![CDATA[
<header>
  <h1>Better Batteries</h1>
  <time class="meta" datetime="2026-08-20">Aug 20, 2026</time>
</header>
<p>One of the eternal schisms in programming is over the question of whether the
standard library should be minimal or encompassing. This is the wrong question
to ask. The right one is:</p>

<figure class="blockquote">
<blockquote><p>Which social architecture creates a high-quality standard library?</p>
</blockquote>

</figure>
<p>Python is always brought up as example of leaky batteries exploding in slow
motion, but this has nothing to do with <em>size</em>. The problem with Python’s stdlib
is its, ahem, uneven quality. Some standard library modules don’t follow
language naming conventions! You know which <code>unittest</code> module I am talking about
:-)</p>
<p>But even that is not a mistake. It’s actually Python core’s advantage — that
it makes functionality available early, not thinking about the future too much.
That’s how we ended up with ossified cAPI which makes CPython the language, but
that is <em>also</em> how we ended up with Python powering data scientific revolution.</p>
<p>The Go standard library is similarly encompassing, but it is held in a high
regard. Go team has institutional capacity to deliver well-designed API for the
standard library, and then some: <a href="https://pkg.go.dev/golang.org/x" class="display url">https://pkg.go.dev/golang.org/x</a></p>
<p>Rust is an interesting case. The 1.0 standard library APIs are brilliant.
Collections and iterators are a work of art. But it also feels that, while the
current team has the capacity to preserve existing APIs and fill in <em>some</em> gaps,
the capacity to execute design decisions is limited. While <code>golang.org/x</code>
captures excess capacity, <code>rust-lang-nursery</code> is a graveyard. Maybe I am
over-indexing on
<a href="https://matklad.github.io/2023/01/04/on-random-numbers.html#True-Randomness">my favorite hobby-horse</a>,
but it seems that the reason for Rust not having an API to get a stream of
random bytes from the OS in 2026 is that, while it is an easy technical problem,
it requires tricky organization architecture (including getting money in
peoples’ pockets, of course) to actually get solved in the high-stakes
environment of a world-wide coordination problem called a programming language.</p>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Printing Lists</title>
    <link href="https://matklad.github.io/2026/08/14/printing-lists.html"
      rel="alternate" type="text/html" title="Printing Lists" />
    <published>2026-08-14T00:00:00+00:00</published>
    <updated>2026-08-14T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/08/14/printing-lists</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[To print a comma-separated list, a concise idiom is to optionally print the comma first, before the element:]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/08/14/printing-lists.html">
      <![CDATA[
<header>
  <h1>Printing Lists</h1>
  <time class="meta" datetime="2026-08-14">Aug 14, 2026</time>
</header>
<p>To print a comma-separated list, a concise idiom is to optionally print the comma first, before the element:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">for</span> (items, <span class="hl-numbers">0</span>..) <span class="hl-operator">|</span>item, item_index<span class="hl-operator">|</span> {</span>
<span class="line">    <span class="hl-keyword">if</span> (item_index &gt; <span class="hl-numbers">0</span>) std.debug.print(<span class="hl-string">&quot;, &quot;</span>);</span>
<span class="line">    std.debug.print(<span class="hl-string">&quot;{}&quot;</span>, .{item});</span>
<span class="line">}</span></code></pre>

</figure>
]]>
    </content>
  </entry>
  <entry>
    <title type="text">Zig&#039;s Io.Threaded is Neat</title>
    <link href="https://matklad.github.io/2026/08/06/neat-io-threaded.html"
      rel="alternate" type="text/html" title="Zig&#039;s Io.Threaded is Neat" />
    <published>2026-08-06T00:00:00+00:00</published>
    <updated>2026-08-06T00:00:00+00:00</updated>
    <id>https://matklad.github.io/2026/08/06/neat-io-threaded</id>
    <author>
      <name>Alex Kladov</name>
    </author>
    <summary type="html">
      <![CDATA[std.Io.Threaded is one of the implementations of Zig's new Io interface that enables concurrency. This is a boring just use threads impl. I personally find it neat though --- it does this weird thing that I wanted to do for ages, that to my knowledge no one else is doing properly, and implements it better than I thought to be possible.]]>
    </summary>
    <content type="html"
      xml:base="https://matklad.github.io/2026/08/06/neat-io-threaded.html">
      <![CDATA[
<header>
  <h1>Zig’s Io.Threaded is Neat</h1>
  <time class="meta" datetime="2026-08-06">Aug 6, 2026</time>
</header>
<p><a href="https://codeberg.org/ziglang/zig/src/commit/16b42da3fd359bf5ae602aa0153e3ec1d6d14822/lib/std/Io/Threaded.zig">std.Io.Threaded</a>
is one of the implementations of Zig’s new <code>Io</code> interface that enables concurrency. This is a boring
“just use threads” impl. I personally find it neat though — it does this weird thing that
<a href="https://github.com/matklad/jthread-rs#jthread">I wanted to do for ages</a>, that to my knowledge no
one else is doing properly, and implements it better than I thought to be possible.</p>
<p><code>Io.Threaded</code> uses blocking syscalls and fully supports cancelation.</p>
<section id="Concurrency-vs-Parallelism">

<h2><a href="#Concurrency-vs-Parallelism">Concurrency vs Parallelism</a></h2>
<p>Quoting <a href="https://www.tedinski.com/2018/10/16/concurrency-vs-parallelism.html">@tedinski</a>,</p>
<ul>
<li>
Concurrency is about handling (asynchronous, nondeterministic) events.
</li>
<li>
Parallelism is about using hardware resources to do more at the same time.
</li>
</ul>
<p>I think this definition is correct, but doesn’t provide useful intuition directly. Concurrency is
the same thing as state transducers? Yes, obviously, but not really illuminating as to how you’d
program the thing.</p>
<p>For intuition, I like these two litmus tests. <em>First</em>, parallelism is deterministic or
“declarative”:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">use</span> rayon::prelude::*;</span>
<span class="line"><span class="hl-keyword">fn</span> <span class="hl-title function_">sum_of_squares</span>(input: &amp;[<span class="hl-type">i32</span>]) <span class="hl-punctuation">-&gt;</span> <span class="hl-type">i32</span> {</span>
<span class="line">    input.<span class="hl-title function_ invoke__">par_iter</span>()</span>
<span class="line">         .<span class="hl-title function_ invoke__">map</span>(|i| i * i)</span>
<span class="line">         .<span class="hl-title function_ invoke__">sum</span>()</span>
<span class="line">}</span></code></pre>

</figure>
<p>You describe how to split the problem into independent partitions, and implement a function to
process one partition at a time . It’s platform’s job to verify the partitioning to be correct
(non-racy), process all partitions, and yield control back once that is done.</p>
<p><em>Second</em>, concurrency invariably involves cancelation. Whenever you have two asynchronous
computations happening at the same time, there comes a moment when one computation becomes aware
that the second computation is no longer necessary, and must be canceled, actively. In general, it
is not possible to just wait until the other computation completes: often, the reason why you want
to cancel it in the first place is precisely because you’ve learned that it <em>can’t</em> complete (e.g.,
it is waiting for a message it will never receive).</p>
<p>And that is the problem with</p>
</section>
<section id="Just-Use-Threads">

<h2><a href="#Just-Use-Threads">Just Use Threads</a></h2>
<p>Well, there are more, the chief being that, while you totally <em>can</em> spawn many threads, this often
requires system-wide configuration change, which is a non-starter for most application. But absence
of cancelation really makes you hit a wall sooner or later. The problem are syscalls. It’s easy
enough, in any loopy code, to do something like</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">while</span> (<span class="hl-literal">true</span>) {</span>
<span class="line">    <span class="hl-keyword">if</span> (is_canceled()) <span class="hl-keyword">return</span> <span class="hl-keyword">error</span>.Canceld; <span class="hl-comment">/// Easy!</span></span>
<span class="line">    ...</span>
<span class="line">}</span></code></pre>

</figure>
<p>But, the thread is instead blocked inside the syscall in the kernel, programming language APIs
generally doesn’t give any way to unblock it:</p>

<figure class="code-block">


<pre><code><span class="line"><span class="hl-keyword">const</span> read_size = <span class="hl-keyword">try</span> read(fd, buffer); <span class="hl-comment">// ???</span></span></code></pre>

</figure>
<p>Wouldn’t it be cool if we could just use standard OS threads, blocking APIs, avoid new shinies like
io_uring, but still get to cancel any work reliably? That’s exactly what Zig’s <code>std.Io.Threaded</code>
provides.</p>
</section>
<section id="SIGIO">

<h2><a href="#SIGIO">SIGIO</a></h2>
<p>The way this works on POSIX is a bit cursed. Turns out, the kernel actually provides a roundabout
way to cancel a blocking syscall — signals. When a thread is blocked in the kernel, and a signal
is delivered to the thread, the thread is woken up and the syscall returns <code>EINTR</code>. It is customary
to just
<a href="https://github.com/rust-lang/rust/blob/f73951df0a5566d94d13b7954acd9f4ab1fa3734/library/alloc/src/io/copy/generic.rs#L240-L244">loop re-try the syscall</a>
in such cases, but one doesn’t have to.</p>
<p>By itself, signals are not a cancelation mechanism — signaling a thread is inherently racy, the
signal might get delivered before the relevant syscall starts, or after it finishes. Conversely, a
syscall might get interrupted by signal unrelated to cancelation.</p>
<p>The actual protocol is that the canceling thread sets a flag in shared memory to request
cancelation, and then signals the cancelee, in a loop, until the cancelation is acknowledged (a
different value for a flag in the shared memory). Upon receiving <code>EINTR</code> from a syscall, the thread
potentially being canceled checks the value of the flag and either retries the syscall, or
acknowledges the cancelation and begins unwinding. See
<a href="https://codeberg.org/ziglang/zig/src/commit/16b42da3fd359bf5ae602aa0153e3ec1d6d14822/lib/std/Io/Threaded.zig#L1266" class="display"><code>signalCanceledSyscall</code></a>
and, eg
<a href="https://codeberg.org/ziglang/zig/src/commit/16b42da3fd359bf5ae602aa0153e3ec1d6d14822/lib/std/Io/Threaded.zig#L10000-L10014" class="display"><code>fileReadPositionalPosix</code></a>
for the two halves of the protocol.</p>
<p>On the user-side, cancelation request is materialized as <code>error.Canceled</code>. Error management as a
feature is a combination of cancelation,
<a href="https://matklad.github.io/2025/11/06/error-codes-for-control-flow.html">branching, and reporting</a>,
and Zig implements the first two. Cancelation isn’t an error <em>not</em> because it is
<a href="https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1677r2.pdf">serendipitous success</a>, but
because, vice versa, an error is a cancelation plus a payload.</p>
<p>On Windows, there’s a much more direct
<code class="display">NtCancelSynchronousIoFile</code>
Love the name!. In general, between fibers, IO Completion Ports, Job objects, and this, it seems
that NT has a better thought through concurrency story than Unix.</p>
</section>
<section id="Prior-Art">

<h2><a href="#Prior-Art">Prior Art</a></h2>
<p>In Java, there’s a similarly looking thread interruption mechanism. Critically, it doesn’t support
interrupting syscalls: <code>IOException</code> and <code>InterruptedException</code> are both checked and unrelated,
meaning that IOing functions are not interruptible. In Zig, reader and writer interfaces completely
type erase errors and therefore support cancelation, though this requires some extra care to handle
correctly, on top of the usual <a href="https://www.youtube.com/watch?v=f30PceqQWko">don’t forget to flush</a>.</p>
<p><code>pthread_cancel</code> implements a similar signal+flag machinery. However, it doesn’t integrate with
language-level cancelation (<code>try</code>, <code>defer</code>) which makes post-cancelation cleanup cumbersome and
slow. More generally, a lot of angst around concurrency stems from a fact that it falls exactly
into the twilight zone between the kernel, the runtime, and the language. There’s almost (interrupts
excepted) no concurrency on the CPU, it’s an illusion with a mixed authorship. The language is
usually the better equipped one to tackle the problem, but, traditionally, it is handled by the
kernel and libc, with adverse effects on language design.</p>
<p>Another problem with <code>pthread_cancel</code> is that it tears down the entire thread, which would be an OK
thing to do if threads were cheap. However, creating threads is still slow, and the configured
system limit for a number of threads is typically low, so its usually a good idea to pool OS
threads. Zig’s <code>Io</code> solves this problem ingeniously, separating, at the interface level,
“may run concurrently” from “must run concurrently”:</p>
<p><a href="https://kristoff.it/blog/asynchrony-is-not-concurrency/" class="display url">https://kristoff.it/blog/asynchrony-is-not-concurrency/</a></p>
<p>This achieves an effect similar to that of <a href="https://en.cppreference.com/cpp/thread/async#Launch_policies"><code>std::launch</code>
<code>policy</code></a> (item 36 in effective modern
C++, if you have that around). By naming what is happening (<code>io.async</code> vs <code>io.concurrent</code>), Zig
makes it easier to understand what is actually going on, and also gets more precise signatures
(<code>concurrent</code> is always fallible, <code>async</code> never is). Of course <code>concurrent</code> is backed by a thread
pool, falling back on spawning a fresh thread only when the pool is exhausted.</p>
</section>
]]>
    </content>
  </entry>
  <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>
</feed>
