November 3āāā9, 2025
Quick bits:
My robovac knocked my glasses onto the ground, and then shoved them in a corner behind furniture. And I somehow need to believe that this is just an accident. Sure.
I contributed to Garen J. Torikianās html-proofer gem, adding support for multiple directory index files. It is now fully usable for my own web site. Iāve integrated it nicely with Nanocās built-in check command, so it runs (and thus must pass) before every deploy.
I switched back from Doppler to Apple Music (the app, not the subscription service). I had been using Doppler [fā¦
November 3āāā9, 2025
Quick bits:
My robovac knocked my glasses onto the ground, and then shoved them in a corner behind furniture. And I somehow need to believe that this is just an accident. Sure.
I contributed to Garen J. Torikianās html-proofer gem, adding support for multiple directory index files. It is now fully usable for my own web site. Iāve integrated it nicely with Nanocās built-in check command, so it runs (and thus must pass) before every deploy.
I switched back from Doppler to Apple Music (the app, not the subscription service). I had been using Doppler for a few weeks, but itās too minimalist for me. It doesnāt have recently played albums, no sorting of albums by year or date added. Those are things I need in a music player and library manager.
Shower thoughts:
Nostalgia isnāt what it used to be.
We have been lied to! Even though it is called āMicrosoft Wordā (singular), you can absolutely use it to type multiple words. They should have called it Microsoft Words. This should save me millions of euros per year!1
Iāve been having trouble sleeping, so Iām trying melatonin. It seems to help with falling asleep, though not with staying asleep. I often wake up at 5ā6 AM which doesnāt quite get me my eight hours of sleep. Though perhaps I do not need those eight hours anyway?
Itās a little early to tell how well it works. At least Iāve got absolutely no side effects, which is fantastic.
Acting class is progressing. With just five weeks left until the performance, itās beginning to get real.
I dropped one of my monologues. Itās not one that I like enough to perform, and Iāve got my hands full with the other two.
Of the two remaining monologues, one Iāve got under my belt. As long as I keep practicing and exploring, Iāll be fine, though I donāt think it requires too much active attention anymore.
The other monologue is a beast. Itās a scene from a 1930s play rewritten to be a monologue, but itās tough to perform, and Iām not even fully done with the edit. (My fiction writing skills sure come into play here.)
Oh, and I mightāve just picked up a proper scene. This is good, because I was getting a bit bored of having only monologues. Fortunately, this is a scene Iāve done before in class, so Iām not starting from zero.
I went second-hand clothes shopping with Jāā and we found some nice props to accentuate the characters Iām playing. It was a good amount of fun, even for one of the shopkeepers who noticed us and said āyep, that looks like character work.ā Weāve been found out!
As a reminder: the performance will be in the evening of Monday, December 15th at theaterforum kreuzberg. More details to follow. Itās open to the public ā drop me a brief message if youāre interested in attending!
Deng, the templating language Iāve been working on, now has the beginnings of an expression language. It now has unary operators, binary operators, functions, and methods. Deng is still not complete, but an expression language is definitely a step in the right direction.
Now I can do stuff like this:
<ol>
{% for items.find_all("/notes/*.md") as note %}
<li>{{ note.title }}</li>
{% endfor %}
</ol>
I havenāt integrated this with my work-in-progress Zig static-site generator yet, but it will happen soon.
My work-in-progress article on expression parsing turned out to be quite useful here, as this is exactly what I needed. Now that I have an expression parser written in Zig, perhaps I can breathe new life into that article with a shift to Zig.
A pending issue is memory management. Iāve skirted around it for now, by assuming that the cleanup of function/method return values lies with the caller. This isnāt ideal, and I will need to revisit that. Some form of garbage collection might be necessary.
Speaking of memory management: in my work-in-progress static-site generator, Iāve struggling to figure out memory management for filters.
A filter2 is a function with this signature:
pub fn call(
allocator: std.mem.Allocator,
str: []const u8,
) ![]const u8 {
// ā¦
}
In other words: take in a string (in other words, a []const u8), and return a string. Think of filters as ātransform Markdown to HTMLā and the like.
Correct memory management means:
deallocating memory that is no longer used. Not doing this leads to memory leaks.
not deallocating memory that is still in use. Not doing this leads to wrong behavior or crashes. This is worse than a memory leak!
Even though the function signature for a filter is simple, it hides quite some complexity. A filter can do a few different things:
It can decide to allocate memory using the given allocator. This is why an allocator is passed in, after all!
It can decide to allocate memory using the C allocator (malloc). This is the case when delegating to a C library.
It can decide not to allocate memory, and return a constant string. For example, there could be a filter that replaces drafts with the constant string "This is a non-public draft.".
It can decide not to allocate memory, and return a slice of the input string. For example, this is what a frontmatter-stripping filter would do.
Iād like the static-site generator to deallocate memory when it is no longer in use, but that is surprisingly difficult to do correctly:3
If the filter returns a constant string, then that return value cannot be deallocated. (Not without an error or a panic, anyway.)
If the filter returns a slice of the input string, then that return value (the slice) cannot be deallocated. After all, that slice was itself not allocated.
Additionally, the input string cannot be immediately deallocated either. The lifetime of the input string needs to cover the lifetime of the returned slice.
An easy way around all this complexity is to mandate that every filter allocates memory for its return value. That sidesteps the problem entirely, but creates inefficiency ā excess memory allocation and copying ā that I want to avoid.
There is another slight complication: strings can have a 0 sentinel value. Zig supports C strings (i.e. not []const u8 but rather [:0]const u8), but itās important that the allocator know whether or not the sentinel value is present to be able to deallocate the memory properly ā the allocation length is checked.
Memory management for filters is complex enough that I need some form of garbage collection. Iām partial to reference-counting implementation, which is what I used for a String type:4
const String = struct {
ref_count: u8 = 1,
content: union(enum) {
//allocated memory
ow ned: struct {
data: []const u8,
allocator: std.mem.Allocator,
},
// allocated memory for C string
owned_z: struct {
data: [:0]const u8,
allocator: std.mem.Allocator,
},
// compile-time constant
constant: []const u8,
// slice of another string
reference: struct {
data: []const u8,
retains: *String,
},
},
}
This looks intimidating ā I know! But itās the simplest implementation that I can come up with that satisfies my needs.
ref_count is the reference count. Easy enough.5 There will be retain() and release() methods, too:
fn retain(self: *@This()) void {
// [snip]
}
fn release(self: *@This()) void {
// [snip]
}
retain() increments the reference count, and release() decrements it. When the reference count hits zero, the release() method deallocates the string.6
The content field can be:
owned or owned_z, which indicates the string has been allocated, with the given allocator, and deallocating the String means deallocating the data. The difference between owned or owned_z is that the latter has a NUL string terminator.
constant_slice is a compile-time constant string whose memory cannot be deallocated. When the String is deallocated, the constant_slice is left as-is.
reference_slice contains a string slice, along with a reference to another String. When this String is deallocated, the referenced string is released (not deallocated).
There is one bit of complexity that Iām fortunately able to ignore: reference cycles. Filters execute linearly; content from earlier filters cannot depend on the output of later filters, so reference cycles wonāt happen.7
So, I was rather pleased with this implementation, until I realized that there is, in fact, one more piece of complexity that I cannot get around ā a piece of complexity that throws a spanner in the works, making the implementation above not practically usable. I had to start over.
If my weekĀnotes had ads, this right here ā right after a cliffhanger ā would be the perfect place to insert an ad break.
Itās called dramatic storytelling, people.
The implementation of reference counting above only works for strings that retain other strings. But this static-site generator will not be limited to dealing with strings:
A filter might return parsed YAML frontmatter whose keys and values reference memory of the incoming string.
A filter might return a rope rather than a string, referencing the incoming string,
A filter might take in a parse tree, and return a rope that references the text nodes in the incoming parse tree.
A filter might take in a lazy stream that references an open file descriptor, and return a lazy stream.
To be clear: my SSG does not yet support anything but strings for content, but itāll need to support different types of content. I want this SSG to be flexible and accommodating.
One approach to supporting multiple types of content would be to allow a String to reference any of these other types:
reference: struct {
data: []const u8,
retains: union(enum) {
string: *String,
rope: *Rope,
json_tree: *JsonTree,
toml_tree: *TomlTree,
xml_tree: *XmlTree,
markdown_tree: *MarkdownTree,
lazy_stream: *LazyStream,
// ...
},
},
But ugh! That retains field is a mess, and the list of tagged union variants undoubtedly will grow over time.
For the sake of simplicity and ease of maintenance, I would much prefer to have polymorphic reference counting. An interface, of sorts: let String, Rope, MarkdownTree etc all implement the Rc interface, and allow a String to retain anything that implements that Rc interface.
Zig, however, does not have interfaces. Still, there is a clean way to do it: allow any reference-countable type to return a vtable (or virtual method table) of type Rc that provides reference-counting methods. Like this:
var thing = Thing.create(...)
// reference count is now 1
thing.rc().retain();
// reference count is now 2
thing.rc().release();
// reference count is now back to 1
thing.rc().release();
// reference count has hit 0
// thing is deallocated
In many programming languages, interfaces are a first-class concept, but in Zig, it simply is a struct. I think it is rather neat. The rc() method is implemented on any reference-countable type like this:
pub fn rc(self: *Thing) Rc {
return .{
.ptr = @ptrCast(self),
.ref_count_ptr = &self.ref_count,
.deinit = rc_deinit,
};
}
For this to work, two things are needed. First, the reference count needs to live on the instance:8
ref_count: u8 = 1,
It also needs a deinit function for deallocating the contents. Iāve opted to have two functions, one that takes a *const anyopaque, casts it to the proper type, and calls the usual deinit():9
fn rc_deinit(ptr: *const anyopaque) void {
const self: *const Thing =
@ptrCast(@alignCast(ptr));
self.deinit();
}
pub fn deinit(self: *Thing) void {
// [snip]
}
Thatās what is needed. Now, the String type can retain anything that implements the Rc interface:
const String = struct {
// [snip]
content: union(enum) {
// [snip]
reference: struct {
data: []const u8,
// This used to be `*String`
retains: Rc,
},
},
}
Hereās how that could work, with one string retaining another string (because the former contains a slice of the memory allocated for the latter):
var orig_string = String.fromOwnedZ(
allocator,
try allocator.dupeZ(u8, "helloworld"),
);
var string = String.fromReferenced(
orig_string.getSlice()[3..6],
orig_string.rc(),
);
orig_string.rc().release();
String.fromReferenced retains the incoming string, which means that orig_string.rc().release() does not deallocate orig_string, as expected.
With this implementation of polymorphic reference counting, I can let a JSON AST retain an incoming string:
var json_string = String.fromOwned(...);
defer json_string.rc().release();
var json_ast: JsonAst = parseJson(json_string);
The parseJson() function has options:
It can retain the incoming string, so that it can safely reference the memory from the incoming string in the JsonAst that it returns. In this case, json_string will not be deallocated by that deferred release() call, as it is still needed.
It can decide to not retain the incoming string, and instead return a JsonAst with standalone, heap-allocated memory. In this case, json_string will not be deallocated by the release() call, as it is no longer needed.
This reference-counting implementation is simple, yet flexible. I am hopeful that this implementation is finally the one that works for me; this is my third or fourth attempt at it, but it looks like this one might be it.
Also: reference counting is neat, isnāt it? I might do a more detailed write-up about polymorphic reference counting in Zig at some point in the future.
That was a lot of writing. I didnāt expect this weekĀnotes entry to turn into a novella. I might be unemployed still, but I am certainly not idle.
Entertainment:
I finished the main quest of Avernum 4: Greed and Glory.10 There are still many, many side quests left. Iāll probably take a break and come back to it later.
I have commenced my playthrough of Warhammer 40,000: Rogue Trader.11 Itās a big game with a well-fleshed out story, great world-building, and nicely challenging combat. Iāll take turn-based combat over realtime any time. Iām still quite early (just finished chapter one) but I can tell Iāll have good fun with this one.
The loading screens, though. There are so many.
I went in to Orlando12 not knowing anything about it, and was surprised ā and delighted ā at how queer and trans it is. Remarkable for its age, and yet it fits in the narrative just fine. While it is arguably a little thin in story, it makes up for that with everything else.
Links:
From the davidlynch community on Reddit: A question David Lynch would like to ask himself: Perfect.
āPrecisionā (Live Jungle!) (Spinscott): I come back to this every year or so. Itās amazing.
This is Why Cycling is Dangerous in America (Not Just Bikes): Ohh, Iāve always thought vehicular cycling was dangerous and inappropriate, and this video gives a good explanation. (Also: plenty of examples for the US in this video also apply to Berlin⦠whose cycling infrastructure leaves much, much to be desired.)
Boards of Canada āOlsonā on a 1959 PDP-1 Computer (Peter Samson and Joe Lynch): Wow!
Whatās On The Edge Of Skyrim? (Any Austin): I will watch every single Any Austin video.
Tech links:
Rethinking Dark Mode Image Strategies with Media Queries in HTML (Sara Soueidan)
Louvre Robbery: Security Flaws: The (Obviously) Password Was āLouvreā: To be confirmed, but that would be pretty awful security.
I woke up at 4 AM with this epiphany front and center in my mind, and at the time it truly felt like my mind had uncovered a deeply fundamental truth about the universe. (I donāt use Microsoft Word.) ā©ļø 1.
āTransformerā might be a better name than āfilter,ā but the name āfilterā is what I am used to, and what Nanoc uses, too. ā©ļø 1.
This list contains what is top of mind right now, and is undoubtedly incomplete. ā©ļø 1.
Reference counting is a form of garbage collection. Itās just not tracing garbage collection. ā©ļø 1.
I could also have used an atomic reference count here. That should be a fairly straightforward change. ā©ļø 1.
Iāve opted to use retain and release, borrowing the terminology that I am familiar with from my Objective-C days. ā©ļø
1.
āReference cycles wonāt happenā feels like exactly the thing that will turn out to be not true simply because I mentioned it. Itās the inherent irony of the universe. ā©ļø 1.
How wide should the reference count be? Iāve opted for a single byte, which seems reasonable in my case. In a more generic reference counting implementation, Iād probably use an u32. ā©ļø 1.
Keeping the old and usual deinit() around means that this implementation can be used without reference counting, too. That flexibility is advantageous. ā©ļø
1.
Avernum 4: Greed and Glory (Spiderweb Software, 2025), published by Spiderweb Software. ā©ļø 1.
Warhammer 40,000: Rogue Trader (Owlcat Games, 2023), published by Owlcat Games. ā©ļø 1.
Orlando, directed by Sally Potter (Adventure Pictures, Lenfilm Studio, Mikado Film, 1993). ā©ļø