<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><id>https://eze.works/tech-notes/</id><title type="text">Software Notes | Eze's Website</title><link href="https://eze.works/atom.xml" rel="self"></link><updated>2026-08-06T19:49:18Z</updated><author><name>Eze Anyanwu</name><uri>https://eze.works/</uri></author><generator>Custom SSG</generator><icon>/assets/favicon.ico</icon><entry><id>https://eze.works/tech-notes/stripe-billing-system</id><title>I built a Stripe-based billing system</title><updated>2026-07-04T00:00:00Z</updated><published>2026-07-04T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/stripe-billing-system" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="I built a Stripe-based billing system | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;I built a Stripe-based billing system | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;I built a Stripe-based billing system&lt;/h1&gt;&lt;p&gt;Sat, Jul 4 2026&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I am at the end of an intense ~4 months of re-building a self-serve billing system backed by the Stripe API at my employer. We flipped on the feature flag a few days ago, making it available for new customers. A few random tidbits of knowledge I collected along the way: &lt;/p&gt;&lt;h2&gt;Parse and validate the Stripe subscription object&lt;/h2&gt;&lt;p&gt;The Stripe subscription API returns json data representing the state of the subscription at that moment. It is likely your integration only considers a subset of subscription states valid. Make that explicit by parsing and validating this json into your own &lt;code&gt;Subscription&lt;/code&gt; object. Design the rest of the code to use this internal subscription object. Make it such that it is easy to create an in-memory representation of your customer that has all the information necessary to understand the state they are in. &lt;/p&gt;&lt;h2&gt;Functional core, imperative shell&lt;/h2&gt;&lt;p&gt;The plan update path is the most involved part of our integration. Instead of discrete plans for a customer to pick from, we allow more fine-grained customization to the features within the plan. Each feature has it's own pricing curve. The pricing of some features depends on the cost of others. The customer can also come back and update their selection mid-cycle. Some changes apply immediately, others are scheduled for the end of their billing cycle. &lt;/p&gt;&lt;p&gt;So a plan update is non-trivial. One strategy I used to tame this complexity is to only make network requests at the "edges" of the system. At one end, we request the Stripe subscription along with other relevant resources like subscription schedule, product, price and discount objects. Database reads are also done at this point. This bundle of data is passed to the "functional" core. This part of the code only operates on in-memory objects and is responsible for implementing the business rules related to updating a plan. It performs no I/O. It returns a set of data structures that represent the parameters needed to update the Stripe subscription into its desired state. At the other edge of the system, we take these output params, create a Stripe API request and send it. &lt;/p&gt;&lt;p&gt;Since Stripe requests are not randomly strewn around your business logic, you can thoroughly test the most important (and potentially most complex) part of your integration in seconds. This is a huge deal for gaining confidence in changes. Billing code tends to be daunting for newcomers because it moves money. Anything we can do to reduce the barrier to maintaing it is welcome. &lt;/p&gt;&lt;p&gt;Furthermore, you now get "preview" functionality for free. With some work, you can take a description of a change a customer would like to make and preview how that would affect their future state without actually modifying anything in Stripe. I think this is pretty cool. &lt;/p&gt;&lt;h2&gt;Try to use Stripe prorations&lt;/h2&gt;&lt;p&gt;If a customer makes a plan change in the middle of their cycle, we credit them for unused time, and potentially charge them for the remaining time on the new product. Stripe subscriptions support this. Use it. Don't do what we did. At the time of writing, Stripe's proration logic is mostly all-or-nothing: Time-based proration for all subscription items, or no proration at all. This felt too restrictive to us. So we initially decided to pass &lt;code&gt;"prorations": "none"&lt;/code&gt; on all our requests and do manual prorations because we had slightly niche logic for one of our products. Right before we shipped the new system, I spent an evening deleting all the manual proration code I had written and using Stripe's proration logic instead.  &lt;/p&gt;&lt;p&gt;I think it's easy to fall into this trap. If it's the first time you are doing it, the proration formula seems easy enough. But it interacts with the rest of your billing decisions in interesting ways. Here are some questions to ask yourself before sinking time into it: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;How bad would it be if you just used Stripe prorations? If it's not a complete deal-breaker for your integration, consider adjusting your business rules so you can use Stripe's default behavior.&lt;/li&gt;&lt;li&gt;Can your subscriptions have flat dollar discounts? If so, think deeply about how these interact with prorations. &lt;/li&gt;&lt;li&gt;Do you ever issue a credit to a customer? If so, how do you know how much they previously paid? Imagine a customer paid on the 1st, someone gave them a discount on the 2nd, and they made a plan change on the 3rd that would involve a credit proration. Though their subscription has a discount, the credit should not consider it since they did not have it when you charged them. &lt;/li&gt;&lt;li&gt;Does your Finance team need proration invoices to look a certain way? A keyword is "revenue recognition". &lt;/li&gt;&lt;li&gt;Do you have any flows where the customer can update their billing cycle immediately? This would involve a credit for unused time on their old plan and a charge for the new plan. If these are two separate invoices, their order matters. If the credit invoice applies after the charge, the customer might not be able to use that credit till their next invoice, which might not be soon. &lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;Incremental progress&lt;/h2&gt;&lt;p&gt;I am not even close to being done with this work. The next chunk of work is migrating old customers to the new system. This will take a while. But it is a big relief to finally merge my 3-month old branch into master. Identifying milestones at which you can ship something is really useful. It is unlikely you'll be able support all possible customer states in one go. Whenever I think I finally understand how things work, Finance surprises me yet again with an example of a weird customer we sold a weird plan to this one time. Release something that works for an initial batch of users, and slowly expand from there. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/speeding-up-an-html-tokenizer</id><title>Speeding up an HTML tokenizer</title><updated>2026-05-29T00:00:00Z</updated><published>2026-05-29T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/speeding-up-an-html-tokenizer" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Speeding up an HTML tokenizer | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Speeding up an HTML tokenizer | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Speeding up an HTML tokenizer&lt;/h1&gt;&lt;p&gt;Fri, May 29 2026&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I wrote and &lt;a href="https://codeberg.org/eze-works/gsx/src/branch/main/tokenizer.go"&gt;HTML parser&lt;/a&gt;, and a week or so ago I became dissatisfied with how the tokenizer was implemented. I made some changes: &lt;/p&gt;&lt;p&gt;Before: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;$ go test --run '$^' --bench 'BenchmarkHtmlTokenizer' --benchmem --count=5
goos: linux
goarch: amd64
pkg: eze.works/go/gsx
cpu: 13th Gen Intel(R) Core(TM) i5-13400T
BenchmarkHtmlTokenizer-16    	       4	 270036084 ns/op	198183348 B/op	 3218584 allocs/op
BenchmarkHtmlTokenizer-16    	       4	 271043490 ns/op	197710172 B/op	 3211666 allocs/op
BenchmarkHtmlTokenizer-16    	       4	 273574498 ns/op	197710152 B/op	 3211666 allocs/op
BenchmarkHtmlTokenizer-16    	       4	 276339333 ns/op	197710200 B/op	 3211667 allocs/op
BenchmarkHtmlTokenizer-16    	       4	 275311635 ns/op	197710160 B/op	 3211666 allocs/op
PASS
ok  	eze.works/go/gsx	5.536s
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;After: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;$ go test --run '$^' --bench 'BenchmarkHtmlTokenizer' --benchmem --count=5
goos: linux
goarch: amd64
pkg: eze.works/go/gsx
cpu: 13th Gen Intel(R) Core(TM) i5-13400T
BenchmarkHtmlTokenizer-16    	       6	 176753957 ns/op	25887232 B/op	  237496 allocs/op
BenchmarkHtmlTokenizer-16    	       6	 174922978 ns/op	25578853 B/op	  232892 allocs/op
BenchmarkHtmlTokenizer-16    	       6	 174573735 ns/op	25578850 B/op	  232892 allocs/op
BenchmarkHtmlTokenizer-16    	       6	 176337642 ns/op	25579737 B/op	  232893 allocs/op
BenchmarkHtmlTokenizer-16    	       6	 185298979 ns/op	25578866 B/op	  232892 allocs/op
PASS
ok  	eze.works/go/gsx	5.363s
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The tokenizer now runs roughly 35% faster and consumes 87% less memory. The benchmark is tokenizing a 16mb file: the raw HTML contents of the full &lt;a href="https://html.spec.whatwg.org/"&gt;HTML spec page&lt;/a&gt;. Go ahead, try to open that link, your browser will probably hiccup &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;I usually append &lt;code&gt;/multipage&lt;/code&gt; to the link when I'm actually trying to reference the spec. &lt;/span&gt;. It's the only page that seems to give my browser a problem. A worthy candidate. &lt;/p&gt;&lt;p&gt;The benchmark is simple. The file is read into memory from disk, then I track the resource usage of tokenizing the whole thing. &lt;label class="sidenote-number" for="sn-2"&gt;2&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-2" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="2"&gt;And by "track", I mean, I just call the function. Go makes it stupid easy to get started with profiling. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;I'll explain the changes I made using the following HTML snippet as an example: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-html" data-lang="html"&gt;&amp;lt;lol&amp;lt; cLAsS\x00="box"&amp;gt;&amp;amp;amp;&amp;lt;/lol"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Imagine that &lt;code&gt;\x00
&lt;/code&gt;is a null byte (i.e. a zero byte). A spec compliant tokenier must interpret this as: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;(tagstart) name=lol&amp;lt; attributes=[(class�, box)]
(text) content=&amp;amp;
(tagend) name=lol"
(eof)
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;No, you are not missing a font. That's the actual unicode replacement character appended to the attribute name as &lt;a href="https://html.spec.whatwg.org/multipage/parsing.html#attribute-name-state"&gt;the spec requires&lt;/a&gt;. &lt;br&gt;For tokenizing HTML, you can't use zero copy techniques to avoid allocating token data. The contents of the returned tokens might not even exist in the input. &lt;/p&gt;&lt;p&gt;The most obvious structure for a returned token is:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-go" data-lang="go"&gt;type Attr struct {
    Name  string 
    Value string
}
type Token struct {
    Kind TokenKind
    Data string
    Attrs []Attr
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;My original tokenizer implementation was creating new &lt;code&gt;string&lt;/code&gt;s as the input was processed.    It would then return a &lt;code&gt;[]Token
&lt;/code&gt;at the very end. &lt;/p&gt;&lt;p&gt;This worked fine, and was not a problem for my usage. But unfortunately I have enough knowledge of how memory works to be disgusted by the repeated allocations the runtime is likely making. Additionally, from what I understand about the Go garbage collector, it needs to scan through all pointers in the program to figure out what objects are still "alive". This seems to imply that generally &lt;label class="sidenote-number" for="sn-3"&gt;3&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-3" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="3"&gt;The Go runtime is complex enough that this is not always true. The only way to be sure is to measure and investigate. &lt;/span&gt;, the more pointers the program has, the more work the garbage colector has to do. &lt;br&gt;My tokenizer was materializing a huge slice of &lt;code&gt;Token&lt;/code&gt; structs, filled to the brim with string pointers to arbitrary places on the heap. I started to feel bad for the machine. &lt;/p&gt;&lt;p&gt;The first change I made was to avoid returning the full list of tokens all at once. I made the tokenizer function like an iterator; returning one token at a time. The complete list is not necessary because the parser only ever processes one token at a time &lt;label class="sidenote-number" for="sn-4"&gt;4&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-4" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="4"&gt;I think this change is worth doing in the context of a GC like go's that traces pointers. Fewer pointers for the GC to trace is a good thing. In the absence of a shadow process using resources to scan your heap, this change might not be as beneficial? Idk. &lt;/span&gt;. &lt;/p&gt;&lt;p&gt;The second change was to stuff all the token data into a contiguous buffer. This buffer is likely to be slightly smaller than the original input, so you can pre-allocate the capacity ahead of time &lt;label class="sidenote-number" for="sn-5"&gt;5&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-5" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="5"&gt;This is actually important. The returned tokens keep the backing buffer alive. If in the middle of tokenizing, the buffer needs to be reallocated, you'll end up with two or more big buffers hanging out in memory. &lt;/span&gt;. This coalesces thousands of little allocations into one big one. So in our example, the tokenizer would gradually accumulate a single buffer whith the following contents: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;lol&amp;lt;class�box&amp;amp;lol"
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The strings within the tokens then become pointers into this buffer, at different offsets. To do this, the tokenizer would need to keep track of where the current token's data starts and ends in this big buffer. Then when a token is about to be emitted you do:    &lt;/p&gt;&lt;pre&gt;&lt;code class="language-go" data-lang="go"&gt;data := buf[start:end]
ptr := unsafe.SliceData(data)
return unsafe.String(ptr, len(data))
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;You can then return the same &lt;code&gt;Token&lt;/code&gt; structure outlined earlier, but the strings are now all pointing into the same allocation. This incantation is necessary because in almost all cases using the natural &lt;code&gt;string(buf[start:end])
&lt;/code&gt;will allocate, defeating the purpose of the buffer. Using &lt;code&gt;unsafe.String&lt;/code&gt; here is safe to do because the buffer is append only, so the underlying bytes that form the string do not change after creation. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/self-referential-rust-data-structures-2</id><title>Self-referential rust data structures</title><updated>2026-04-22T00:00:00Z</updated><published>2026-04-22T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/self-referential-rust-data-structures-2" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Self-referential rust data structures | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Self-referential rust data structures | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Self-referential rust data structures&lt;/h1&gt;&lt;p&gt;Wed, Apr 22 2026&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Here is a candid shot of me reading &lt;a href="https://kyju.org/blog/tokioconf-2026/"&gt; this blog post&lt;/a&gt; about the pain of self-referential data structures in rust. &lt;/p&gt;&lt;img src="/assets/images/self-referential-rust.png" alt="Self-referential rust data structure meme."&gt;&lt;p&gt;Seeing someone who clearly knows more than me also struggle here was very validating. It's interesting that once you get into this space, the same names, blog posts and techniques start popping up. It's full of tradeofs. Are you ok with not freeing individual items in your self-referential structure? Do you need stuff to be shared across threads? Are you OK with leaking your use of an arena to users of your library? Do you have brain cells to spare? &lt;/p&gt;&lt;p&gt;My own experience in this comes from evolving my &lt;a href="https://codeberg.org/eze-works/htmlite"&gt; very own HTML toolkit&lt;/a&gt;. One of the things the library allows you to do is construct and manipulate HTML nodes. To do this you need to build a DOM-like structure, which implies parent, sibling, and child pointers. I've re-implemented this library at least three times. Once using bumpalo, another time using a plain Vec with indices and the third (and final) time using strong/weak pointers. The first two were techniques covered in the blog post. &lt;/p&gt;&lt;p&gt;I admit that once the article wandered into "Generativity", my eyes starting glazing over. I don't doubt that for the author's use case, the type gymnastics become necessary. And really, that's the point of their talk. But there is a sad user experience cliff here I think. The other option would be to give up and use unsafe, but then you'd have ​g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s problems. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/the-right-datastructure</id><title>Pick the right data structure</title><updated>2026-01-25T00:00:00Z</updated><published>2026-01-25T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/the-right-datastructure" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Pick the right data structure | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Pick the right data structure | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Pick the right data structure&lt;/h1&gt;&lt;p&gt;Sun, Jan 25 2026&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I was reminded yesterday of the importance of finding the right data structure for a problem. I have this custom HTML parser I've written, and part of the implementation involves detecting named character references. Named character references are things like &lt;code&gt;&amp;amp;amp;&lt;/code&gt; or &lt;code&gt;&amp;amp;gt;&lt;/code&gt;. The HTML spec defines the complete &lt;a href="https://html.spec.whatwg.org/multipage/named-characters.html"&gt; list&lt;/a&gt;. There are about 2000 of them, and a good parser needs to recognize them and replace them with the unicode bytes they represent. &lt;/p&gt;&lt;p&gt;There are a few things to keep in mind: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;The only reasonable way to parse HTML is byte by byte &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;Technically you iterate over unicode scalar values, but for this explanation, that detail does not matter. &lt;/span&gt;. &lt;/li&gt;&lt;li&gt;Some of the named character references are prefixes to other ones (e.g, &lt;code&gt;&amp;amp;notin;&lt;/code&gt; and &lt;code&gt;&amp;amp;notindot&lt;/code&gt;). So you can't simply stop on the first match. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;My initial implementation stored the named character references in a hardcoded map. Once I hit an ampersand, I would begin accumulating subsequent bytes into a temporary buffer. For each new byte, I would iterate through the keys of the map, looking for named character references that started with whatever was currently in my temporary buffer. This worked, so I moved on. &lt;/p&gt;&lt;p&gt;Yesterday, I was finally putting the library to real use. When loaded with a browser, the HTML file in question happened to have ~2.2MB of source html/css/js. Not that big, but much bigger than any input I had used while testing the HTML parser. Parsing was taking ~8 seconds. &lt;/p&gt;&lt;p&gt;&lt;code&gt;pprof&lt;/code&gt; revealed the CPU was spending most of its time in the state for parsing character references. This makes sense: I was looping through &lt;em&gt;all&lt;/em&gt; the keys in the map for each byte inside that state. But it wasn't necessary to do all that work. All I wanted to do was to check if at least one element in the hardcoded map started with the contents of my temporary buffer. With that fixed, runtime was down to ~3 seconds. Progress, but &lt;code&gt;pprof&lt;/code&gt; still showed the same function taking up most of the execution time. &lt;/p&gt;&lt;p&gt;There were no real improvements I could make without changing how I stored and accessed the character reference map. I needed a way to quickly identify which character references were prefixed by the current contents of the temporary buffer. I knew about tries and that they were supposed to solve this exact problem, but I was not familiar with them and had not implemented one before. &lt;/p&gt;&lt;p&gt;So I pasted my code into Claude and asked: &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;I've written an html tokenizer in go. From profiling, it seems a lot of time is spent in the named character state. Specifically iterating through the pre-built character reference map. What can I do to speed that up? &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Claude: &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Option 1: &lt;/p&gt;&lt;p&gt;Use a Trie Data Structure (Best Option) Replace the map with a trie (prefix tree) for O(n) lookups where n is the length of the reference, not the number of entries: &lt;/p&gt;&lt;/blockquote&gt;&lt;pre&gt;&lt;code class="language-go" data-lang="go"&gt;type trieNode struct {
    children    map[rune]*trieNode
    replacement string // non-empty if this is a valid end point
}

type charRefTrie struct {
    root *trieNode
}

func buildCharRefTrie() *charRefTrie {
    root := &amp;amp;trieNode{children: make(map[rune]*trieNode)}
    
    for key, value := range characterReferenceMap {
        node := root
        for _, ch := range key {
            if node.children[ch] == nil {
                node.children[ch] = &amp;amp;trieNode{children: make(map[rune]*trieNode)}
            }
            node = node.children[ch]
        }
        node.replacement = value
    }
    
    return &amp;amp;charRefTrie{root: root}
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;I pasted this code verbatim into my project, adjusted the named character state, fixed an off-by-one error and profiled again: &lt;strong&gt;76ms&lt;/strong&gt;&lt;label class="sidenote-number" for="sn-2"&gt;2&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-2" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="2"&gt;There could be a whole other post here about how useful LLMs are for learning from first principles. This is not that post. &lt;/span&gt;. &lt;/p&gt;&lt;p&gt;The fix makes sense intuitively: by using trie, I am slowly narrowing down the list of potential named character references with each byte, instead of looking at all of them over and over again. But a speedup from 8000ms to 76ms almost makes me ashamed of how wasteful the original implementation was. All because I picked the wrong data structure. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/rust-anyhow</id><title>Why isn't anyhow implemented using Box&lt;dyn std::error::Error&gt;?</title><updated>2025-12-13T00:00:00Z</updated><published>2025-12-13T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/rust-anyhow" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Why isn't anyhow implemented using Box&amp;lt;dyn std::error::Error&amp;gt;? | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Why isn't anyhow implemented using Box&amp;lt;dyn std::error::Error&amp;gt;? | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Why isn't anyhow implemented using Box&amp;lt;dyn std::error::Error&amp;gt;?&lt;/h1&gt;&lt;p&gt;Sat, Dec 13 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;The &lt;a href="https://crates.io/crates/anyhow"&gt; anyhow&lt;/a&gt; crate is a popular pick for handling errors in Rust applications. The protagonist of the crate is the &lt;code&gt;anyhow::Error&lt;/code&gt; type, which can wrap anything that implements &lt;code&gt;std::error::Error&lt;/code&gt;&lt;/p&gt;&lt;p&gt;If I were to implement a catch-all error type to wrap any other error type, I would probably define it like this: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;struct Error {
    inner: Box&amp;lt;dyn std::error::Error&amp;gt;
}

impl&amp;lt;E: std::error::Error + 'static&amp;gt; From&amp;lt;E&amp;gt; for Error {
    fn from(value: E) -&amp;gt; Error {
        Error { inner: Box::new(value) }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;&lt;code&gt;anyhow&lt;/code&gt; does it &lt;a href="https://github.com/dtolnay/anyhow/blob/2c0bda4ce944d943e7141f0316b0ea996602238e/src/lib.rs#L390"&gt; quite differently&lt;/a&gt;: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;#[repr(transparent)]
pub struct Error {
    inner: Own&amp;lt;ErrorImpl&amp;gt;,
}

#[repr(C)]
pub(crate) struct ErrorImpl&amp;lt;E = ()&amp;gt; {
    vtable: &amp;amp;'static ErrorVTable,
    backtrace: Option&amp;lt;Backtrace&amp;gt;,
    _object: E,
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;It's written this way to save space. My &lt;code&gt;Error&lt;/code&gt; struct is 16 bytes long &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;Assuming a 16-bit architecture &lt;/span&gt;because a boxed trait object is represented as a "fat pointer". The &lt;code&gt;anyhow&lt;/code&gt; implementation is &lt;a href="https://github.com/dtolnay/anyhow/commit/727f36ffd001013c78db99863dcca79de198b375"&gt; originally based on the &lt;code&gt;fehler&lt;/code&gt; crate&lt;/a&gt;, which in turn &lt;a href="https://without.boats/blog/failure-to-fehler/"&gt; is based on the &lt;code&gt;failure&lt;/code&gt; crate&lt;/a&gt;. Upon hitting 1.0, fehler's author wrote &lt;a href="https://without.boats/blog/failure-1.0/"&gt; a blog post&lt;/a&gt;, which contained this illuminating nugget about a new feature flag: &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The Error type, which uses heap allocation and dynamic dispatch, is designed for cases in which errors are very infrequent. For this reason, it is valuable to avoid pessimizing the happy path by making the Result type overly large. By default, the Error type is the size of two pointers - one to some data in the heap, and one to a vtable. With the small-error feature turned on, the size is cut in half to one pointer. This works by storing the vtable inline inside of the Error type’s heap representation, instead of storing it next to the heap allocated pointer. The interior of the Error type is then a dynamically sized type, but the pointer to it just a single pointer instead of a wide pointer like trait objects are. &lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;So the grandfather of &lt;code&gt;anyhow&lt;/code&gt; was &lt;a href="https://github.com/rust-lang-deprecated/failure/blob/65c825aeaa6b07a51e4fb1d824625485061604f4/src/error/error_impl.rs#L7C1-L7C29"&gt; implemented the same way I would do it&lt;/a&gt;&lt;label class="sidenote-number" for="sn-2"&gt;2&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-2" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="2"&gt;In &lt;a href="https://blog.rust-lang.org/2018/06/21/Rust-1.27/#dyn-trait"&gt; older versions of rust&lt;/a&gt;, you would write &lt;code&gt;Box&amp;lt;Trait&amp;gt;&lt;/code&gt; to denote a boxed trait object. &lt;/span&gt;, and the vtable business is an optimization that unfortunately pessimizes the clarity of the code. &lt;/p&gt;That leaves that weird &lt;code&gt;Own&lt;/code&gt; type: &lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;#[repr(transparent)]
pub struct Own&amp;lt;T&amp;gt;
where
    T: ?Sized,
{
    pub ptr: NonNull&amp;lt;T&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;In fact, the file where it's defined contains two other types: &lt;code&gt;Ref&lt;/code&gt; &amp;amp; &lt;code&gt;Mut&lt;/code&gt;. Since vtables involve playing around with raw pointers, these types were added to &lt;a href="https://github.com/dtolnay/anyhow/commit/62673e2ccf8f0b20519c3a610f8d8b5aaf99e6f9"&gt; give back some measure of type safety&lt;/a&gt; when doing so. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/thinking-about-tech-debt</id><title>How I think about technical debt</title><updated>2025-12-12T00:00:00Z</updated><published>2025-12-12T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/thinking-about-tech-debt" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="How I think about technical debt | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;How I think about technical debt | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;How I think about technical debt&lt;/h1&gt;&lt;p&gt;Fri, Dec 12 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Working at a venture-funded startup has added some nuance to how I think about technical debt: it's not about the debt itself, but the interest rate. &lt;/p&gt;&lt;p&gt;You begin accumulating technical debt the moment you check in some new code. By checking the code in, you are committing your team to the continued maintenance of the functionality you added. On its own, there is nothing inherently good or bad about this debt. What matters is the ongoing cost: the time and effort it takes to change or understand the code later. This is the &lt;i&gt;interest&lt;/i&gt; on the technical debt. If a piece of code makes people go "kinda terrified to change the logic here tbh", then you are paying high interest on this code whenever someone interacts with it. The only code with a 0% interest rate is no code at all. &lt;/p&gt;&lt;p&gt;Writing code is only worth it when the benefits it grants are worth the maintenance costs.  It might be worth it to write high-interest code if you think it will hardly be changed, but think hard about writing medium-interest code for foundational parts of the system that will be frequently touched. &lt;/p&gt;&lt;p&gt;This type of thinking makes it possible to talk more clearly about paying off technical debt. It is possible to build a collective understanding around how certain corners of a system cost more to change than others. It's likely that high-interest parts of the code are more prone to bugs. Working on high-interest code does not spark joy. I might even try to avoid it by taking on technical debt elsewhere. Given enough churn in a high-interest part of the codebase, there are real impacts on new feature velocity, employee retention and product health. This is language that startups understand. The problem is no longer about some code being inherently "bad" with the only solution being to "fix it". It is now about lowering the interest the company is paying on the code, which can take many forms: rewriting it, refactoring it, replacing it with a third-party library, requesting fewer changes in the affected area etc. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/at-proto-is-interesting</id><title>The AT protocol is interesting</title><updated>2025-10-24T00:00:00Z</updated><published>2025-10-24T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/at-proto-is-interesting" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="The AT protocol is interesting | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;The AT protocol is interesting | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;The AT protocol is interesting&lt;/h1&gt;&lt;p&gt;Fri, Oct 24 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Although I have had a bluesky account for almost a year now, I have not really given the technology it is built on, AT proto, much thought. Until yesterday. &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;This surge is likely because I enabled &lt;a href="https://pi-hole.net/"&gt; pi-hole&lt;/a&gt; on my local network and immediately blocked &lt;code&gt;*.ycombinator.com&lt;/code&gt;. I suddenly have a lot of free, productive time. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;These three resources whet my appetite to learn more: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="https://overreacted.io/open-social/"&gt; "Open Social"&lt;/a&gt;&lt;a class="archive-link" href="https://web.archive.org/web/20251015031714/https://overreacted.io/open-social/"&gt;archived&lt;/a&gt;: I thought this explained the core of "Why AT proto" so well, I read it to my (non-technical) wife, and even she started to get it. I really think the ability to take your social media connections, likes, follows, media and general digital belongings elsewhere is appealing to regular folk. &lt;/li&gt;&lt;li&gt;&lt;a href="https://atproto.com/articles/atproto-for-distsys-engineers"&gt; "AT Proto for Engineers"&lt;/a&gt;&lt;a class="archive-link" href="https://web.archive.org/web/20251017082623/https://atproto.com/articles/atproto-for-distsys-engineers"&gt;archived&lt;/a&gt;: This is a wonderful high level view of how the protocol works. Lots of diagrams. Clearly explained. 10/10 have read through 3 times. It's titled "for distributed system engineers", but I think that's misleading; I think any software developer generally familiar with how web apps &amp;amp; backends &amp;amp; databases work would understand this. &lt;/li&gt;&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=fU9hR3kiOK0"&gt; "Turning a database inside out"&lt;/a&gt;&lt;a class="archive-link" href="https://web.archive.org/web/20250904205127/https://www.youtube.com/watch?v=fU9hR3kiOK0"&gt;archived&lt;/a&gt;: A talk on applying database implementation details to general application design. If you liked the previous link, then this is good supporting material. &lt;/li&gt;&lt;/ul&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/basic-css-selector-engine</id><title>Implementing a basic css selector engine</title><updated>2025-10-02T00:00:00Z</updated><published>2025-10-02T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/basic-css-selector-engine" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Implementing a basic css selector engine | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Implementing a basic css selector engine | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Implementing a basic css selector engine&lt;/h1&gt;&lt;p&gt;Thu, Oct 2 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I recently found myself forced to write a CSS selector engine. Figuring it out was extremely rewarding, so it thought it might be useful to write down the intuition about how you might do this. &lt;/p&gt;&lt;p&gt;By "CSS engine", I am referring to code that takes a css selector like &lt;code&gt;p &amp;gt; [data-selected="yes"]
&lt;/code&gt;and uses it to find matching HTML nodes. This assumes that you have a tree of HTML nodes to search over. The specific implementation does not matter, but your node API needs to have a way to get previous, preceding, parent and ancestor elements given a starting node. &lt;/p&gt;&lt;p&gt;The goal is to have an API that allows you to do this: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-pseudocode" data-lang="pseudocode"&gt;for node in start.select("p &amp;gt; li[href]") {
  // do something
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;I'll use the following selector as an example: &lt;code&gt;body &amp;gt; span p + li&lt;/code&gt;.   Think of it this way: The selector imposes certain constraints that nodes must satisfy in order to match: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;The node must be a "li" element.&lt;/li&gt;&lt;li&gt;At least one of the nodes that matched the preivous step must have an immediate preceding sibling that is a "p" element.&lt;/li&gt;&lt;li&gt;At least one of the nodes that matched the previous step must have an ancestor that is a "span" element.&lt;/li&gt;&lt;li&gt;At least one of the nodes that matched the previous step must have a direct parent that is a "body" element.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;We break down to the selector into four "checks": &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;[body "&amp;gt;"] [span " "] [p "+"] [li]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Each check specifies what to look for and where to look for it. The "what to look for" part is what the CSS specification calls a "compound selector"; Things like "div", "#container" and &lt;code&gt;"[data-state|='selected']".
&lt;/code&gt;The "where to look for it" part is what the CSS specification calls "combinators"; Things like "&amp;gt;", "~", and "+". To check if an element &lt;code&gt;A&lt;/code&gt; matches a selector, we need to pass &lt;code&gt;A&lt;/code&gt; "through" this stack of checks. Every check performs the same steps: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;Takes a node as input&lt;/li&gt;&lt;li&gt;Applies the combinator &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;But what combinator do we apply to the last &lt;code&gt;li&lt;/code&gt; selector? For my algorithm, I decided to tack on a synthetic "identity" combinator that just passes on the node it was given. So if you get a &lt;code&gt;li&lt;/code&gt;, you are going to be using that same &lt;code&gt;li&lt;/code&gt; in the next step (as opposed to its ancestors or previous sibling for example). &lt;/span&gt;to the node to transform it into the list of nodes to consider. &lt;/li&gt;&lt;li&gt;Tests the compound selector against each node we are considering. If a node matches, pass it on to the next check. &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;An element matches if at least one of these paths makes it through the entire stack of checks. &lt;/p&gt;&lt;p&gt;&lt;a href="https://codeberg.org/eze-works/gsx/src/commit/4f7c4a7a070f22213fd7ab966266f52c0da3eead/selector.go#L99"&gt; Here&lt;/a&gt; is my my version of this. It is surprisingly not that much code at all once you figure out the how to do it. Pretty neat! &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/just-a-matter-of-time</id><title>Just a matter of time</title><updated>2025-09-21T00:00:00Z</updated><published>2025-09-21T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/just-a-matter-of-time" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Just a matter of time | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Just a matter of time | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Just a matter of time&lt;/h1&gt;&lt;p&gt;Sun, Sep 21 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I'm really happy with my latest creation: &lt;a href="https://crates.io/crates/htmlite"&gt; htmlite&lt;/a&gt;. It's an html toolkit for parsing, manipulating and generating html. It's currently part of the tooling that powers this site.    &lt;/p&gt;&lt;p&gt;I'm not proud of it because it's technically interesting or anything, but because it took a lot of patience to work through. The core of the library is an implementation of the &lt;a href="https://html.spec.whatwg.org/multipage/parsing.html#tokenization"&gt; HTML tokenization spec&lt;/a&gt;. It's quite long for starters. At the time of writing, it's described as a state machine with 80 states. To someone unfamiliar with specs like this, it took a while to understand how to read the thing. It also takes some creative thinking to translate some of the spec operations into code. &lt;/p&gt;&lt;p&gt;But it was worth it! Parsing HTML was something I once thought I wasn't smart enough to do properly. That is no longer the case and it feels wonderful. It didn't take some big revelation or jump in intelligence. It was just a matter of time. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/stop-doing-templating-languages</id><title>Stop doing templating languages</title><updated>2025-09-02T00:00:00Z</updated><published>2025-09-02T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/stop-doing-templating-languages" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Stop doing templating languages | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Stop doing templating languages | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Stop doing templating languages&lt;/h1&gt;&lt;p&gt;Tue, Sep 2 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I think HTML templating languages like jinja2, handlebars and go templates are the wrong solution. If what you are doing makes you reach for these tools, consider using a general programming language instead. HTML templating languages limit you to the subset of general programming patterns the language author has chosen to implement. &lt;/p&gt;&lt;p&gt;You don't have to do a lot of templating before thinking to yourself: &lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;It would be nice to extract this section and use it in all these other places without repeating myself too much. &lt;/p&gt;&lt;cite&gt;&amp;amp;mdash;A developer creating a site using an HTML templating language, probably &lt;/cite&gt;&lt;/blockquote&gt;&lt;p&gt;I hope the templating language you picked supports arbitrary user-defined snippets with the capacity to pass arguments. If you were using a normal language, you would use a regular function and move on. &lt;/p&gt;&lt;p&gt;This is mostly a public reminder to myself because I foolishly once thought &lt;a href="https://codeberg.org/eze-works/html-string"&gt; writing a templating language was a good idea&lt;/a&gt;. I learned a lot in the process though, so I have no regrets. Once I got the basics working, I quickly realized that to make it useful, you basically have to implement variables, functions, arguments, loops etc... And at that point, why not just use an existing language? &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/rust-bumpalo</id><title>A peek inside the rust bumpalo crate</title><updated>2025-08-30T00:00:00Z</updated><published>2025-08-30T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/rust-bumpalo" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="A peek inside the rust bumpalo crate | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;A peek inside the rust bumpalo crate | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;A peek inside the rust bumpalo crate&lt;/h1&gt;&lt;p&gt;Sat, Aug 30 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Using the rust &lt;a href="https://crates.io/crates/bumpalo"&gt; bumpalo&lt;/a&gt; crate has opened my eyes to what you can do with alternative memory allocation strategies. I usually don't think to look at the code of low-level libraries; I'm afraid I won't understand. But I'm trying to get rid of that habit. Humans wrote this code after all, surely I &lt;i&gt;can&lt;/i&gt; understand it given enough time and patience. &lt;/p&gt;&lt;h1&gt;Why bumpalo?&lt;/h1&gt;&lt;h2&gt;The problem&lt;/h2&gt;&lt;p&gt;I briefly covered that in a &lt;a href="/tech-notes/self-referential-rust-data-structures"&gt; previous note&lt;/a&gt;. I needed a collection of nodes where (among other things) each node keeps track of its siblings. I chose to do this using shared references; each node stores shared references to its next and previous sibling. These references need to point &lt;i&gt;somewhere&lt;/i&gt;, so I need a collection that owns the actual nodes:  &lt;/p&gt;&lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;use std::cell::Cell;

#[derive(Default)]
struct MyNode&amp;lt;'a&amp;gt; {
    next: Cell&amp;lt;Option&amp;lt;&amp;amp;'a MyNode&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    prev: Cell&amp;lt;Option&amp;lt;&amp;amp;'a MyNode&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
}

fn main() {
    let mut nodes = vec![
        MyNode::default(),
        MyNode::default(),
        MyNode::default(),
    ];

    // We can manipulate the pointers!
    // This is the begining of a nice tree API!
    &amp;amp;nodes[0].next.set(Some(&amp;amp;nodes[1]));
    &amp;amp;nodes[1].prev.set(Some(&amp;amp;nodes[0]));
    &amp;amp;nodes[1].next.set(Some(&amp;amp;nodes[2]));
    &amp;amp;nodes[2].prev.set(Some(&amp;amp;nodes[1]));
    
    // Uh oh. I guess not
    // Error: cannot borrow `nodes` as mutable because it is also borrowed as immutable
    // nodes.push(MyNode::default());
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Everything is great until the last line.  The crux of the problem is that you can't be sure that a pointer to an element in a &lt;code&gt;Vec&lt;/code&gt; continues to be valid after you've added a new element to the &lt;code&gt;Vec&lt;/code&gt;; it could have copied all its elements somewhere else and your pointer now points to garbage. The rust compiler prevents you from doing this by enforcing that you can't have any existing references into the vec when calling &lt;code&gt;nodes.push&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;Thank you &lt;code&gt;rustc&lt;/code&gt;, but that disrupts my plans &amp;gt;:(  &lt;/p&gt;&lt;h2&gt;The solution&lt;/h2&gt;&lt;p&gt;Thankfully &lt;code&gt;bumpalo&lt;/code&gt; exists. You can think of it as &lt;code&gt;Vec&lt;/code&gt; that lets you push a new element into it and guarantees that all existing references are stable. &lt;/p&gt;&lt;p&gt;When you push an object into a &lt;code&gt;Vec&lt;/code&gt;, it asks the system memory allocator for a chunk of memory, and stores your object in there. As you keep pushing, you use up that space. At some point you won't have any more space. &lt;/p&gt;&lt;p&gt;&lt;code&gt;Vec&lt;/code&gt; handles this by asking the system memory allocator for a bigger chunk of space, copying all existing objects to that new space and appending your new element. &lt;/p&gt;&lt;p&gt;&lt;code&gt;bumpalo&lt;/code&gt; does something different: It asks the system memory allocator for a bigger chunk of space, records the address of the old chunk of space in it, then adds your new element to it. &lt;/p&gt;&lt;p&gt;This means that existing references remain valid! The following compiles: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;use std::cell::Cell;
use bumpalo::Bump;

#[derive(Default)]
struct MyNode&amp;lt;'a&amp;gt; {
    next: Cell&amp;lt;Option&amp;lt;&amp;amp;'a MyNode&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    prev: Cell&amp;lt;Option&amp;lt;&amp;amp;'a MyNode&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
}

fn main() {
    // The collection of nodes
    let nodes = Bump::new();
    
    let a = nodes.alloc(MyNode::default());
    let b = nodes.alloc(MyNode::default());
    let c = nodes.alloc(MyNode::default());

    // We can manipulate the pointers
    a.next.set(Some(b));
    b.prev.set(Some(a));
    b.next.set(Some(c));
    c.prev.set(Some(b));
    
    nodes.alloc(MyNode::default());
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;h1&gt;So how does bumpalo do it?&lt;/h1&gt;&lt;p&gt;Diagram time. This is what a non-empty &lt;code&gt;Bump&lt;/code&gt; looks like: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;+----------+
| metadata |
+----------+ &amp;lt;--------------------------- +
                                          | 
+------------------------+----------+ --- +
|___CCCCCCBBBBBBBBBBBAAAA| metadata |
+------------------------+----------+ &amp;lt;--------------------------- +
                                                                   |
+------------------------------------------------+----------+ ---- +
|_____FFFFFFFFFFFFFFFFFEEEEEEEEEDDDDDDDDDDDDDDDDD| metadata | 
+------------------------------------------------+----------+ &amp;lt;--- +
                                                                   |  
+------------------------+ --------------------------------------- +
| let bump = Bump::new() |
+----------------------- +
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Our non-empty &lt;code&gt;bump&lt;/code&gt; variable points to a sort of linked list of data + &lt;code&gt;metadata&lt;/code&gt; objects. In the code, &lt;code&gt;metadata&lt;/code&gt; is called a &lt;a href="https://github.com/fitzgen/bumpalo/blob/573ed78f2c0a6d9f888f87f8c2a6c4acce4ddadc/src/lib.rs#L301"&gt;&lt;code&gt;ChunkFooter&lt;/code&gt;&lt;/a&gt;, and is where the book-keeping information is stored. &lt;/p&gt;&lt;p&gt;The cells with letters (e.g. A, B, C) represent arbitrary bytes. Consecutive cells of the same letter represent one "object" that was allocated in the bump. Unlike &lt;code&gt;Vec&lt;/code&gt;, &lt;code&gt;Bump&lt;/code&gt; can store &lt;i&gt;any&lt;/i&gt; type of object. &lt;/p&gt;&lt;p&gt;What does an empty bump look like? &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;+----------+
| metadata |
+----------+
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;It's represented as an &lt;a href="https://github.com/fitzgen/bumpalo/blob/573ed78f2c0a6d9f888f87f8c2a6c4acce4ddadc/src/lib.rs#L334"&gt; "empty" chunk footer&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;The first call to &lt;code&gt;Bump::alloc(&amp;lt;blah&amp;gt;)&lt;/code&gt; causes bumpalo to request a chunk of empty space from the system memory allocator. It creates a new chunk footer with &lt;code&gt;ChunkFooter::prev&lt;/code&gt; pointing to the initial empty chunk footer and writes it to the end of the space it was just given. &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;+----------+
| metadata |
+----------+ &amp;lt;--------------------------- +
                                          |
+------------------------+----------+ --- +
|________________________| metadata |   
+------------------------+----------+
                         ^
                         ^
      [metadata.ptr points here now]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The new chunk's &lt;code&gt;ChunkFooter::ptr&lt;/code&gt; is a pointer that starts out pointing to the start of &lt;code&gt;metadata&lt;/code&gt;. To allocate our object, we calculate how many bytes we need to store it, then &lt;a href="https://github.com/fitzgen/bumpalo/blob/573ed78f2c0a6d9f888f87f8c2a6c4acce4ddadc/src/lib.rs#L1927"&gt; decrement ptr by that amount&lt;/a&gt;. We then write our object to the space pointed to by the footer's newly updated &lt;code&gt;ptr&lt;/code&gt;. &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;+----------+
| metadata |
+----------+ &amp;lt;--------------------------- +
                                          |
+------------------------+----------+ --- +
|__________________AAAAAA| metadata |   
+------------------------+----------+
                   ^
                   ^
      [metadata.ptr points here now]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;If the object we are trying to allocate is larger than the availble space in the current chunk, we allocate another chunk, setting it's  &lt;code&gt;ChunkFooter::prev&lt;/code&gt; pointer to the old one. And so on. &lt;/p&gt;&lt;p&gt;And that's the core of it really as far as I can tell. There is a lot of code to handle proper alignment calculation, optimizations and allocating more complex objects. But at the heart of bumpalo is a linked list of heap-allocated byte buffers. Because objects that are allocated are never moved, even when appending the linked list: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;Bump can add elements without requiring a unique reference&lt;/li&gt;&lt;li&gt;You can keep shared references to objects in bump for as long as the bump lives.&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;What are the tradeoffs of this approach?&lt;/h2&gt;&lt;p&gt;&lt;code&gt;bumpalo&lt;/code&gt; is not a magical "better &lt;code&gt;Vec&lt;/code&gt;". &lt;/p&gt;&lt;ul&gt;&lt;li&gt;Its construction guarantees that references to items within it remain stable, so you can't de-allocate individual items.&lt;/li&gt;&lt;li&gt;Because it uses &lt;code&gt;Cell&lt;/code&gt;, you can't share references to &lt;code&gt;Bump&lt;/code&gt; across threads (though you &lt;i&gt;can&lt;/i&gt; move the owned &lt;code&gt;Bump&lt;/code&gt; into a thread). &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;👾&lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/self-referential-rust-data-structures</id><title>Self-referential Rust structs using arena allocation</title><updated>2025-08-14T00:00:00Z</updated><published>2025-08-14T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/self-referential-rust-data-structures" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Self-referential Rust structs using arena allocation | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Self-referential Rust structs using arena allocation | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Self-referential Rust structs using arena allocation&lt;/h1&gt;&lt;p&gt;Thu, Aug 14 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;A lot of ink has been spilled on the topic of self-referential structures in Rust. I won't pile on. &lt;/p&gt;&lt;p&gt;I stumbled across a different pattern for achieving them in this &lt;a href="https://github.com/SimonSapin/rust-forest"&gt; github repo&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;I found it useful for creating a tree of nodes that refer to each other after parsing some HTML. &lt;/p&gt;&lt;p&gt;&lt;strong&gt;Arena Allocator + &lt;code&gt;std::cell::Cell&lt;/code&gt;&lt;/strong&gt;: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-rust" data-lang="rust"&gt;pub struct Node&amp;lt;'a&amp;gt; {
    kind: NodeKind&amp;lt;'a&amp;gt;,
    parent: Cell&amp;lt;Option&amp;lt;&amp;amp;'a Node&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    previous_sibling: Cell&amp;lt;Option&amp;lt;&amp;amp;'a Node&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    next_sibling: Cell&amp;lt;Option&amp;lt;&amp;amp;'a Node&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    first_child: Cell&amp;lt;Option&amp;lt;&amp;amp;'a Node&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
    last_child: Cell&amp;lt;Option&amp;lt;&amp;amp;'a Node&amp;lt;'a&amp;gt;&amp;gt;&amp;gt;,
}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;... where &lt;code&gt;'a&lt;/code&gt; is the lifetime of the arena allocator &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;&lt;a href="https://crates.io/crates/bumpalo"&gt; bumpalo&lt;/a&gt; has served me well. &lt;/span&gt;you are using. &lt;/p&gt;&lt;p&gt;This works because: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;The compiler knows the size of the struct. All the &lt;code&gt;Node&lt;/code&gt;s are behind a &lt;code&gt;&amp;amp;&lt;/code&gt; indirection.&lt;/li&gt;&lt;li&gt;Cell allows you to retrieve and mutate values from behind shared references (i.e. &lt;code&gt;&amp;amp;&lt;/code&gt;) as long as the value is &lt;code&gt;Copy&lt;/code&gt;. &lt;code&gt;&amp;amp;Node&lt;/code&gt; is &lt;code&gt;Copy&lt;/code&gt; because it's a pointer. &lt;/li&gt;&lt;li&gt;Parsing functions can receive an arena allocator as argument, allocate their nodes into the arena and return references to nodes whose lifetime is tied to the arena.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;For my use case: &lt;/p&gt;&lt;ul&gt;&lt;li&gt;It is much better than using indices instead of references because you don't have to pass around the the list of objects everywhere.&lt;/li&gt;&lt;li&gt;It is much better than &lt;code&gt;Rc&amp;lt;RefCell&amp;lt;...&amp;gt;&amp;gt;&lt;/code&gt; because you don't have to worry about overlapping &lt;code&gt;borrow_mut()&lt;/code&gt; calls.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;It feels like alien tech 👾 &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/make-stuff-up</id><title>Make stuff up</title><updated>2025-06-17T00:00:00Z</updated><published>2025-06-17T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/make-stuff-up" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Make stuff up | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Make stuff up | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Make stuff up&lt;/h1&gt;&lt;p&gt;Tue, Jun 17 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;A recent &lt;i&gt;Signals &amp;amp; Threads&lt;/i&gt; episode touched on how Jane street &lt;a href="https://signalsandthreads.com/building-tools-for-traders/#002103"&gt; uses their own custom protocols for network communication&lt;/a&gt;. Internally, their services do not use HTTP, but instead speak a custom protocol over TCP/UCP. I vaguely remember a similar thing being mentioned by Matt Godbolt in his Two's Complement podcast; he was talking about how &lt;a href="https://twoscomplement.org/#podcast/weird-webapps"&gt; web dev in finance firms looks very different&lt;/a&gt;. Their UI's usually just open up a websocket connection and speak a custom protocol with the server. &lt;/p&gt;&lt;p&gt;I wish more people built exactly what they needed. No one cares about your needs more than you do, so inventing a protocol that solves your exact problems shouldn't be off the table &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;As long as you document it.&lt;/span&gt;. You do not need permission to deviate from the well-worn, standard path. It's software! It's mostly an imaginary world to begin with. You can go your own way when it makes sense. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/parsing-network-streams</id><title>Parsing network streams</title><updated>2025-06-11T00:00:00Z</updated><published>2025-06-11T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/parsing-network-streams" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Parsing network streams | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Parsing network streams | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Parsing network streams&lt;/h1&gt;&lt;p&gt;Wed, Jun 11 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I have often wondered what the "right" way to parse network streams is. Unlike parsing a file or something in-memory, parsing from the network often involves the annoying call to &lt;code&gt;read()&lt;/code&gt;. It's annoying because you don't know how many bytes you'll get back. You could get 1 byte. You could also get 4k bytes. &lt;/p&gt;&lt;p&gt;My first stab at getting around this was to always read 1 byte at a time &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;On its own, parsing byte-by-byte would be "slow" because each call to &lt;code&gt;read()&lt;/code&gt; would involve an "expensive" network syscall. But I was doing this in Rust, which comes with a &lt;a href="https://doc.rust-lang.org/std/io/struct.BufReader.html#"&gt; free abstraction around buffered reading&lt;/a&gt;, making this not such a bad idea. &lt;/span&gt;, and make my parsing code a state machine. Parsing with a state machine is "cool" in a weird sort of way, but it's also a pain to maintain and understand, especially for protocols as lenient as HTTP/1.1. &lt;/p&gt;&lt;p&gt;I have since moved away from that approach. For HTTP/1.1 in particular, I have found that it's much easier to buffer until you see a byte that indicates the end of a section. For example, to read the request line, you continuously read from the network socket until you see a &lt;code&gt;\r\n
&lt;/code&gt; or &lt;code&gt;\n
&lt;/code&gt;, then you take that chunk and parse it. &lt;/p&gt;&lt;p&gt;Until today, I wasn't sure how applicable this approach is to other protocols. To my surprise, the Beej guy that wrote the guide to network programming in C also &lt;a href="https://www.beej.us/guide/bgnet0/html/split/parsing-packets.html"&gt; recently wrote about just this topic&lt;/a&gt;. It appears that this "buffering until you get a complete message" is exactly how this sort of thing is done. &lt;/p&gt;&lt;p&gt;This is a nice affirmation that if you spend enough time trying to re-invent a wheel, you'll eventually stumble upon the best practices around wheel making. All on your own. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/mix-is-actually-simple</id><title>Elixir's mix tool is actually simple</title><updated>2025-05-26T00:00:00Z</updated><published>2025-05-26T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/mix-is-actually-simple" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Elixir's mix tool is actually simple | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Elixir's mix tool is actually simple | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Elixir's mix tool is actually simple&lt;/h1&gt;&lt;p&gt;Mon, May 26 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;The mix tool is an elixir script. It's entry point is &lt;code&gt;Mix.ClI.main()&lt;/code&gt;. When you execute &lt;code&gt;mix&lt;/code&gt; at your shell &lt;a href="https://github.com/elixir-lang/elixir/blob/c39286ef9c0b795b42137eca6319b508c13d4ace/bin/mix"&gt; that is all that is called&lt;/a&gt;&lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;Remember that &lt;a href="/tech-notes/notes-on-elixir-compilation"&gt; elixir is written in erlang&lt;/a&gt;. Erlang is able to find the compiled &lt;code&gt;Mix.CLI&lt;/code&gt; module because the elixir shell script gives the &lt;code&gt;erl&lt;/code&gt; executable a list of places to look for compiled beam modules. &lt;/span&gt;. &lt;/p&gt;&lt;p&gt;The Mix.CLI module simply compiles the mix.exs file in the current directory and stores the configuration in memory. It then looks for a Task with the same name as the argument it was given, compiles it and executes it. &lt;/p&gt;&lt;p&gt;Tasks define their own command line arguments, help, and behavior. It's a neat way to structure an extendable command line program. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/deep-dive-into-elixir-eex</id><title>How does Elixir's EEx work?</title><updated>2025-05-25T00:00:00Z</updated><published>2025-05-25T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/deep-dive-into-elixir-eex" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="How does Elixir's EEx work? | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;How does Elixir's EEx work? | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;How does Elixir's EEx work?&lt;/h1&gt;&lt;p&gt;Sun, May 25 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Elixir's builtin templating library, EEx, is nifty. The more I understand it, the more interesting it becomes. Unlike most of the standard library, the documentation for EEx is sparse. But fear not, &lt;a href="https://blog.nelhage.com/post/computers-can-be-understood/"&gt; all software can be understood&lt;/a&gt;. &lt;/p&gt;&lt;h2&gt;How do templating engines usually work?&lt;/h2&gt;&lt;p&gt;On the surface, EEx is like other templating languages: the API accepts arbitrary text optionally sprinkled with special placeholders that are evaluated with a runtime-supplied object. &lt;/p&gt;&lt;p&gt;They all look like this: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-js" data-lang="js"&gt;Template.render("Hello {{ @who }}", { who: "world" })
=&amp;gt; "Hello world"
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;That's where the similarity ends. Most template libraries take the input text and break it into "instructions": &lt;/p&gt;&lt;pre&gt;&lt;code class="language-text" data-lang="text"&gt;"Hello {{ @who }}!"
      |
      |
      v
[Text("Hello "), Expr("@who"), Text("!")]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;They then go through this list, executing the code that matches the instruction using the runtime-provided object to lookup variables, and building up the output string along the way. More complex constructs like loops can be implemented by emitting "goto" instructions and keeping track of a loop counter. &lt;label class="sidenote-number" for="sn-1"&gt;1&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-1" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="1"&gt;A good, readable example of this is the &lt;a href="https://github.com/bheisler/TinyTemplate/tree/master"&gt; TinyTemplate&lt;/a&gt;. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;These libraries "execute" the template as they go, just like interpreters. &lt;/p&gt;&lt;p&gt;EEx on the other hand is more of a compiler. When you call it, it does not output a string. It outputs Elixir code. &lt;/p&gt;&lt;h2&gt;EEx outputs Elixir code!&lt;/h2&gt;&lt;p&gt;Note that I'll be only talking about the &lt;a href="https://hexdocs.pm/eex/1.18.4/EEx.html#compile_string/2"&gt;&lt;code&gt;EEx.compile_string/2&lt;/code&gt;&lt;/a&gt; function, as all other functions in the EEx module depend on it. &lt;/p&gt;&lt;p&gt;The docs for EEx do explicitely say that this function compiles a string into an Elixir syntax tree. But I was still surprised when I actually internalized it. &lt;/p&gt;&lt;p&gt;Take the string &lt;code&gt;one &amp;lt;%= "two" %&amp;gt; three &amp;lt;%= "four" %&amp;gt; five&lt;/code&gt; for example. If you gave the equivalent of this to another templating library , you would expect to get back &lt;code&gt;one two three four five&lt;/code&gt;. But that is not what you get from EEx. &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;iex(5)&amp;gt; EEx.compile_string(~s(one &amp;lt;%= "two" %&amp;gt; three &amp;lt;%= "four" %&amp;gt; five))
{:__block__, [],
 [
   {:=, [],
    [
      {:arg0, [], EEx.Engine},
      {{:., [], [{:__aliases__, [alias: false], [:String, :Chars]}, :to_string]},
       [], ["two"]}
    ]},
   {:=, [],
    [
      {:arg1, [], EEx.Engine},
      {{:., [], [{:__aliases__, [alias: false], [:String, :Chars]}, :to_string]},
       [], ["four"]}
    ]},
   {:&amp;lt;&amp;lt;&amp;gt;&amp;gt;, [],
    [
      "one ",
      {:"::", [], [{:arg0, [], EEx.Engine}, {:binary, [], EEx.Engine}]},
      " three ",
      {:"::", [], [{:arg1, [], EEx.Engine}, {:binary, [], EEx.Engine}]},
      " five"
    ]}
 ]}
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;I'll walk through how to read this indented data structure in another post, but for now it's important to understand that this data structure &lt;i&gt;is&lt;/i&gt; Elixir code. The Elixir code you write is actually the textual/human representation of this data. When you execute the textual representation of Elixir code, the compiler transforms it to this data structure. It's just Elixir tuples. Just like any other pieace of data, you can save this to a variable, write it to a file, or even send it over the network. &lt;/p&gt;&lt;p&gt;The docs call this an Abstract Syntax Tree" or a quoted expression. I think that distinction blurs the fact that this datastructure &lt;i&gt;is&lt;/i&gt; the language. And the language &lt;i&gt;is&lt;/i&gt; data. &lt;label class="sidenote-number" for="sn-2"&gt;2&lt;/label&gt;&lt;input class="sidenote-toggle" id="sn-2" type="checkbox"&gt;&lt;span class="sidenote-content" data-sn-number="2"&gt;Hey! that &lt;a href="https://en.wikipedia.org/wiki/Lisp_%28programming_language%29"&gt; sounds familiar&lt;/a&gt;. &lt;/span&gt;&lt;/p&gt;&lt;p&gt;You can see the human representation of this data structure: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;iex(8)&amp;gt; EEx.compile_string(~s(one &amp;lt;%= "two" %&amp;gt; three &amp;lt;%= "four" %&amp;gt; five))
        |&amp;gt; Macro.to_string()
        |&amp;gt; IO.puts()
arg0 = String.Chars.to_string("two")
arg1 = String.Chars.to_string("four")
&amp;lt;&amp;lt;"one ", arg0::binary, " three ", arg1::binary, " five"&amp;gt;&amp;gt;
:ok
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Instead of printing out &lt;code&gt;one two three four five&lt;/code&gt;, EEx handed us a sequence of expressions that evaluate to a binary/string with the contents &lt;code&gt;one two three four five&lt;/code&gt;. It compiled the string template to executable code. &lt;/p&gt;&lt;h2&gt;So how does it work?&lt;/h2&gt;&lt;p&gt;The &lt;code&gt;EEx.Compiler&lt;/code&gt; module converts the input template into a list of what I'll be calling "chunks". For example: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;~s(one &amp;lt;%= "two" %&amp;gt; three &amp;lt;%= "four" %&amp;gt; five)
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;... gets converted to ... &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;[
  {:text, ~c"one ", %{line: 1, column: 1}},
  {:expr, ~c"=", ~c" \"two\" ", %{line: 1, column: 5}},
  {:text, ~c" three ", %{line: 1, column: 17}},
  {:expr, ~c"=", ~c" \"four\" ", %{line: 1, column: 24}},
  {:text, ~c" five", %{line: 1, column: 37}},
  {:eof, %{line: 1, column: 42}}
]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;Text outside the special markers is represented as a tuple tagged with &lt;code&gt;:text&lt;/code&gt;. Elixir expressions inside the special markers are tagged with &lt;code&gt;:expr&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;The docs don't explicitly mention this, but EEx also supports block syntax. Tuples tagged with &lt;code&gt;:start_expr&lt;/code&gt;, &lt;code&gt;:middle_expr&lt;/code&gt; and &lt;code&gt;:end_expr&lt;/code&gt; are used for this case. You can do this: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;"""
Listing:
&amp;lt;%= for post &amp;lt;- posts do %&amp;gt;
  Name: &amp;lt;%= post.title %&amp;gt;
  Href: &amp;lt;%= post.href %&amp;gt;
&amp;lt;% end %&amp;gt;
"""
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;... or even this ... &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;"""
&amp;lt;%= if true do %&amp;gt;
  "something"
&amp;lt;% else %&amp;gt;
  "something else"
&amp;lt;% end %&amp;gt;
"""
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The last example gets converted to the following chunks: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;[
  {:start_expr, ~c"=", ~c" if true do ", %{line: 1, column: 1}},
  {:text, ~c"\n  \"something\"\n", %{line: 1, column: 18}},
  {:middle_expr, [], ~c" else ", %{line: 3, column: 1}},
  {:text, ~c"\n  \"something else\"\n", %{line: 3, column: 11}},
  {:end_expr, [], ~c" end ", %{line: 5, column: 1}},
  {:text, ~c"\n", %{line: 5, column: 10}},
  {:eof, %{line: 6, column: 1}}
]
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The &lt;code&gt;EEx.Compiler.compile&lt;/code&gt; function calls the similarly named function in the &lt;code&gt;EEx.Engine&lt;/code&gt; module for each of these chunks, pasing in the chunk as well as accumulated state. &lt;/p&gt;&lt;p&gt;In the default &lt;code&gt;EEx.Engine&lt;/code&gt; implementation, the accumulated always has this structure: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;%{ binary: [ ... ], dynamic: [ ... ], vars_count: num }
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;This makes sense when you realize that compiling the template always returns code with the following structure: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-elixir" data-lang="elixir"&gt;# The "dynamic" section
# A variable declaration for each non text "chunk" in the template
arg0 = String.Chars( ... )
arg1 = String.Chars( ... )

# The "binary" section
# A binary as the last expression
&amp;lt;&amp;lt;"verbatim text from template", arg0::binary , "text", arg1::binary&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;The &lt;code&gt;vars_count&lt;/code&gt; is used to keep a counter to generate the argX variable names. The &lt;code&gt;binary&lt;/code&gt; list contains the &lt;code&gt;:text&lt;/code&gt; chunks. The &lt;code&gt;dynamic&lt;/code&gt; list contains AST/quoted expressions/Elixir code for the expressions inside the special markers. &lt;/p&gt;&lt;p&gt;The job of &lt;code&gt;EEx.Engine&lt;/code&gt; is to build up this accumulated state as it's handed the chunks of the template. &lt;code&gt;EEx.Engine.handle_body&lt;/code&gt; is called at the very end with the accumulated state in order to build generate the Elixir code. The "secret weapon" that makes this possible is that Elixir already exposes &lt;a href="https://hexdocs.pm/elixir/1.18.4/Code.html#string_to_quoted/2"&gt; functions&lt;/a&gt; that take the textual representation of Elixir code and turn it into data. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/notes-on-elixir-compilation</id><title>Notes on elixir compilation</title><updated>2025-05-23T00:00:00Z</updated><published>2025-05-23T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/notes-on-elixir-compilation" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Notes on elixir compilation | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Notes on elixir compilation | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Notes on elixir compilation&lt;/h1&gt;&lt;p&gt;Fri, May 23 2025&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;Elixir is not a self-hosted language. The compiler is implemented in erlang. It is organized into various modules in &lt;a href="https://github.com/elixir-lang/elixir/tree/a7703a9ee246e35f14bea7fc78b5bb7fca2c039b/lib/elixir/src"&gt; lib/elixir/src&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;The &lt;code&gt;elixir&lt;/code&gt; and &lt;code&gt;elixirc&lt;/code&gt; commands are shell scripts that run the &lt;code&gt;erl&lt;/code&gt; command, &lt;a href="https://github.com/elixir-lang/elixir/blob/a7703a9ee246e35f14bea7fc78b5bb7fca2c039b/bin/elixir#L222"&gt; giving it the compiled &lt;code&gt;elixir&lt;/code&gt; erlang module, and executing its &lt;code&gt;start_cli&lt;/code&gt; function&lt;/a&gt;. &lt;/p&gt;&lt;p&gt;Because the elixir compiler is written in erlang, bootstraping is straightforward. The &lt;a href="https://github.com/elixir-lang/elixir/blob/a7703a9ee246e35f14bea7fc78b5bb7fca2c039b/Makefile#L99"&gt; makefile&lt;/a&gt;: &lt;/p&gt;&lt;ol&gt;&lt;li&gt;Compiles the elixir compiler written in erlang into erlang bytecode.&lt;/li&gt;&lt;li&gt;Starts the now-compiled elixir compiler application, and uses it to compile the standard library that is written in Elixir&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;Put simply, the elixir compiler is erlang code that recognizes elixir syntax, and transforms it into the &lt;a href="https://www.erlang.org/doc/apps/erts/absform.html"&gt; erlang Abstract Format&lt;/a&gt;. Don't let the name scare you though, this "Abstract Format" is very concrete and is essentially erlang data-structures representing the AST of erlang code. The elixir compiler then feeds this Abstract Format into the erlang &lt;a href="https://www.erlang.org/doc/apps/compiler/compile.html"&gt;&lt;code&gt;compile&lt;/code&gt;&lt;/a&gt; module. &lt;/p&gt;&lt;p&gt;I think it's pretty neat that erlang exposes a function to compile erlang AST into bytecode that can run on the erlang VM. This is probably a big reason why the elixir compiler can be maintained by a &lt;a href="https://github.com/josevalim"&gt;single person&lt;/a&gt;. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/optimizing-html-images</id><title>Optimizing HTML images as a post-processing step</title><updated>2024-03-30T00:00:00Z</updated><published>2024-03-30T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/optimizing-html-images" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Optimizing HTML images as a post-processing step | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Optimizing HTML images as a post-processing step | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Optimizing HTML images as a post-processing step&lt;/h1&gt;&lt;p&gt;Sat, Mar 30 2024&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;I have been working on a new marketing site for my employer. The design includes a lot of high resolution image assets. To keep the site snappy, it's useful to avoid downloading huge images when not necessary. On a mobile phone for example, the image could be several time smaller without looking different to the human eye. The &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images"&gt; standard approach&lt;/a&gt; to optimize images is to generate multiple versions of the image at different sizes, then use the image &lt;code&gt;srcset&lt;/code&gt; attribute to tell the browser which version to load. These are called "responsive images". &lt;/p&gt;&lt;p&gt;This is what the markup for a responsive image might look like: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-html" data-lang="html"&gt;&amp;lt;img
  srcset="elva-fairy-480w.jpg 480w, elva-fairy-800w.jpg 800w"
  sizes="(max-width: 600px) 480px,
         800px"
  src="elva-fairy-800w.jpg"
  alt="Elva dressed as a fairy" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;I don't want to manually figure this out whenever I need to insert an image. We are using &lt;a href="https://gohugo.io/"&gt; Hugo&lt;/a&gt;, so my first thought was to write a &lt;a href="https://gohugo.io/templates/partials/"&gt; partial&lt;/a&gt;&lt;a class="archive-link" href="https://web.archive.org/web/20240313102218/https://gohugo.io/templates/partials/"&gt;archived&lt;/a&gt; that did all the work to make a simple &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tag responsive. Like a gift from above, that same day, &lt;a href="https://news.ycombinator.com/item?id=39816836"&gt; jampack was posted to Hacker News&lt;/a&gt;. &lt;a href="https://jampack.divriots.com/"&gt; Jampack&lt;/a&gt;&lt;a class="archive-link" href="https://web.archive.org/web/20240329135252/https://jampack.divriots.com/"&gt;archived&lt;/a&gt; is a CLI tool that takes a static site as input (e.g. a folder with a bunch of HTML, CSS, JS &amp;amp; assets) and optimizes it by doing exactly what I had set out to do and more. This means I can continue to write standard unresponsive &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; tags but still get responsive images in the final output of the site. All for the cheap price of four new words added to the build script: &lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash" data-lang="bash"&gt;npx @divriots/jampack ./public
&lt;/code&gt;&lt;/pre&gt;&lt;button data-copy-snippet="copy again|copied!"&gt;copy snippet&lt;/button&gt;&lt;p&gt;My joy is immense. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry><entry><id>https://eze.works/tech-notes/time-for-computers</id><title>Time for Computers</title><updated>2023-11-04T00:00:00Z</updated><published>2023-11-04T00:00:00Z</published><author><name>Eze Anyanwu</name></author><link href="https://eze.works/tech-notes/time-for-computers" rel="alternate"></link><content type="html">&lt;!DOCTYPE html&gt;&lt;html lang="en"&gt;&lt;head&gt;&lt;meta charset="utf-8"&gt;&lt;meta content="width=device-width, initial-scale=1" name="viewport"&gt;&lt;meta content="Time for Computers | Eze's Website" name="description"&gt;&lt;link href="/atom.xml" rel="alternate" title="Software notes" type="application/atom+xml"&gt;&lt;link href="/assets/favicon.ico" rel="icon"&gt;&lt;link href="/assets/css/fonts.css" rel="stylesheet"&gt;&lt;link href="/assets/css/reset.css" rel="stylesheet"&gt;&lt;link href="/assets/css/base.css" rel="stylesheet"&gt;&lt;link href="/assets/css/post-index.css" rel="stylesheet"&gt;&lt;link href="/assets/css/prose.css" rel="stylesheet"&gt;&lt;title&gt;Time for Computers | Eze's Website&lt;/title&gt;&lt;/head&gt;&lt;body&gt;&lt;nav&gt;&lt;a href="/"&gt;About&lt;/a&gt;&lt;a href="/tech-notes"&gt;Notes&lt;/a&gt;&lt;a href="/recs"&gt;Links&lt;/a&gt;&lt;/nav&gt;&lt;main&gt;&lt;article class="prose"&gt;&lt;hgroup&gt;&lt;h1&gt;Time for Computers&lt;/h1&gt;&lt;p&gt;Sat, Nov 4 2023&lt;/p&gt;&lt;/hgroup&gt;&lt;p&gt;The basic unit for CPU time is a nanosecond. This is too small a measure to develop an intuition for. But you can build intuition by comparison. In the "Time for Computers" episode of the &lt;a href="https://www.twoscomplement.org/"&gt; Two's Complement&lt;/a&gt; podcast, Matt Godbolt tries to tackle this by comparing the CPU time scale with a human time scale. This really helped me, so i'm reproducing a hand-wavy transcription of that scale here: &lt;/p&gt;&lt;p&gt;The basic unit of work a CPU can do is an instruction cycle. During the cycle the CPU will fetch an instruction, decode it and execute it. &lt;/p&gt;&lt;p&gt;CPUs take 1 cycle to do elementary operations like addition, subtraction, XOR's and such. A CPU cycle takes a third of a nanosecond. Charitably, we'll say a human doing the same kind of operation would take 1 second. &lt;/p&gt;&lt;p&gt;So that's our scale:  &lt;i&gt;1/3 nanosecond for a CPU is like  1 second for a homo sapiens&lt;/i&gt;. &lt;/p&gt;&lt;p&gt;The next type of operation a CPU does is &lt;i&gt;multiplying&lt;/i&gt;. It takes anywhere between 4 and 6 cycles for a CPU to do multiplication. That works out as ~1.3 nanoseconds, which in human time is 4 seconds. Now that's still plausible. You could imagine someone who is good at mental math taking about that long to compute 398 times 16 (...i am not good at mental math). &lt;/p&gt;&lt;p&gt;Next up is &lt;i&gt;division&lt;/i&gt; If you don't have some sort of look up table, you will need to use pencil and paper to manually calculate. That intuition is about right for CPUs. They can't do division that much better than humans can. Integer division is anywhere between 30 - 100 cycles (10 - 33 ns), which in our scale is anywhere between 30 seconds to a minute and a half of human time. This makes sense if you imagine the CPU needing to take out a pencil and paper. &lt;/p&gt;&lt;p&gt;CPUs read from memory as well. We are told that memory is slow, which is why we have CPU caches that are supposed to make things go faster. &lt;/p&gt;&lt;p&gt;An access to &lt;i&gt;L1 cache&lt;/i&gt; is the fastest thing you can get. It's a tiny cache right next to the CPU on the order of 32K bytes in size. It takes 3 CPU cycles to read from L1, which is ~3 seconds in human terms. That's a bit like retrieving information from the sticky-note on your desk. &lt;/p&gt;&lt;p&gt;&lt;i&gt;L2 cache&lt;/i&gt; is a bigger, further away cache. If we were comparing L1 to a sticky-note, L2 is like a set of ring-binders you have on the shelves behind you. Accessing L2 is 10 cycles away, which in human terms would be 10 seconds away. Seems a bit quick for a human, but it's within the realm of possibility. &lt;/p&gt;&lt;p&gt;&lt;i&gt;L3&lt;/i&gt; is the final cache layer shared between CPUs. That takes about ~40 cycles to get information, which is 40 seconds in human time. &lt;/p&gt;&lt;p&gt;Now if have to hit &lt;i&gt;main memory&lt;/i&gt;, we are talking 100 - 120 nanoseconds. That is 6 minutes of human time. A trip down the elevator to the archives to get the book you need and go back up the elevator and back up to the office to put it in cache. In the working life of a computer whose working job is adding numbers together, that's twiddling your thumbs or taking a tea break. That's why all these performance geeks hate missing their cache so much &lt;/p&gt;&lt;p&gt;Now for the scary part. Reading from an &lt;i&gt;SSD&lt;/i&gt; takes about 50 microseconds (not nano anymore!)...which is 2 whole days of human time. That's ordering something on amazon.com whenever you ask your CPU to read a file from disk. Disgusting. I will never use disk again /s. &lt;/p&gt;&lt;p&gt;If you are using a spinning disk whose head  is not positioned in the right place and has to seek to the right sector, we are talking 1 - 10 milliseconds...which is 1 - 12 months in human months... &lt;/p&gt;&lt;p&gt;At the far end of this scale is rebooting the computer. Assuming it takes 5 minutes to do so (plausible depending on your computer), that's 32 millennia in human years. A civilization-ending event for your CPU. Think again before rebooting your computers. &lt;/p&gt;&lt;/article&gt;&lt;/main&gt;&lt;footer&gt;&lt;section class="footer-links"&gt;&lt;a href="https://codeberg.org/eze-works"&gt;Git&lt;/a&gt;&lt;a href="https://www.linkedin.com/in/ezeanyanwu/"&gt;LinkedIn&lt;/a&gt;&lt;a href="mailto:hello@ezeanyinabia.com"&gt;Email&lt;/a&gt;&lt;a href="https://eze.works/atom.xml"&gt;Atom Feed&lt;/a&gt;&lt;/section&gt;&lt;section class="git-info"&gt;&lt;p&gt;Built from git commit &lt;code&gt;&lt;a href="https://codeberg.org/eze-works/eze.works/commit/d3c204eae7584dccfcb7a16309e9755d74380723"&gt;d3c204e&lt;/a&gt;&lt;/code&gt;&lt;/p&gt;&lt;/section&gt;&lt;/footer&gt;&lt;script src="/assets/js/main.js" type="module"&gt;&lt;/script&gt;&lt;/body&gt;&lt;/html&gt;</content></entry></feed>