This was my week off and I wanted to learn some more Zig. What I did: dug into the Zig compiler, wrote toy programs to replicate parts of it, tried to understand the Zig way of doing things. I’ve also spent two days hacking a GTK feature into Ghostty, which is largely written in Zig. Feature is not done yet (god no), but it’s probably been the most serious Zig coding I’ve done so far.
So I figured I’d send you some early, rough, subjective thoughts on learning Zig. This is not a comprehensive analysis of Zig and reasons for or against learning it. It’s thoughts that went through my head this week while writing Zig.
It’s very data-oriented. Data structures and smart use of them are everywhere. I found that whenever you learn a new language, there’s one thing that immediately sticks out: learn Lisp and it’s the syntax, learn Rust and it’s the type system and borrow checker, learn Ruby and everything’s an object, learn Lua and wow it’s so tiny. With Zig the main thing that stands out to me is how much attention is paid to clever use of data structures. Read this article on the Zig parser to see what I mean. After reading that, I dug into how the compiler does string interning and found a clever use of hash maps. It’s very, very interesting and I feel like I’m gaining another pair of glasses with which to look at programming.
It’s a simple language, but not that simple. There are quite a few syntactic structures that I keep stumbling over, there’s still compiler errors I can’t make sense of, and I haven’t fully grokked comptime yet (more on that in next point). I suspect it’s due to the last point: comptime.
Comptime allows you to run Zig code at compile-time. It allows you to write Zig code to do meta-programming: create types at compile-time (generics!), unroll loops at compile-time, do some neat stuff with pointers, and so on – mind-blowingly cool stuff. But I still have a hard time grasping it.
Comptime makes Zig lazy and that still trips me up (probably because I haven’t internalized comptime yet). Take this code:
const Person = struct {
age: u8,
};
fn newPerson(age: u8) Person {
// BUG: This field name is wrong
// vvvvvvvvvv
return .{ .ageInYears = age };
}
test "newPerson" {
const std = @import("std");
try std.testing.expectEqual(5, 5);
}
Note the bug. ZLS, the Zig language server, doesn’t tell me there’s a problem and Zig happily compiles and runs this test:
φ zig test lazy.zig
All 1 tests passed.
Why? Because the newPerson
function isn’t called in the test.
Again: this probably trips only me up because lack of understanding of comptime. (Told you: not an expert-analysis, but raw, dumb thoughts)
Expressions are everywhere and I love it. I love that
switch
can be used as an expression, I love thatif
can be an expression. I love that even loops can be used as expressions.After this week of coding, I probably need to go back and re-read a few things. That’s the best way for me to learn: read something, try it out in practice, go back to reading.
It feels very early. I first played around with Rust in, I think, 2015 and that felt very early, but Zig feels even earlier. There’s not a ton of standardized documentation, everybody’s using the latest/nightly version of Zig, Zig’s still changing, the package manager story is being written right now, patterns are still emerging, community is full of hackers that already know a lot of other languages and are interested in compilers and languages. It’s nice, it’s raw, I enjoy it, but you need to adjust your expectations accordingly.
The best learning resources I’ve found so far: ziglearn.org, Learning Zig, Ziglings, the stdlib. Zig Discord is immensely helpful, too: I had two questions of mine answered within 15min.
Last thought after this week off and also probably related to Zig: I love programming very much.
it is lazy
The ghostty url has a typo (extra 'k' at the end)