zig #
notes on zig, kept as idioms annotated with the release they were learned on. what changed between releases lives apart, in versions: the 0.15 format and io overhauls, the 0.16 migration, and 0.17's migration and silent changes.
language #
zig has no string type, so text is bytes and the
vocabulary for characters is yours to choose. it has no traits either, so
interfaces are comptime duck typing,
type-returning functions, or vtables. most of the leverage is in
comptime: generating types, validating sql before it
runs, and knowing when the branch quota or @inComptime will bite. a struct
copies by value, which decides how structs should
hold strings, and a file sitting beside its directory is how
modules stay navigable. testing
is where tests vanish without a word: unresolved files, comptime folding, and a
failed command label on suites that passed. inline assembly sits here too,
because cpu kernels live or die on assembler
feature checks and register budgets.
the standard library #
arraylist and hashmap both split into
managed and unmanaged forms, and what matters is who owns the slice afterwards.
json writes through Stringify and parses into an arena;
binary wire formats (CBOR, CAR, varints) take any writer.
compression decodes zstd with no vendored C, and
crypto keeps ecdsa somewhere other than where you would
look. logging goes through std.log and one logFn hook,
and a cli touches most of what is left: stdout against stderr,
broken pipes, spawning a pager.
since 0.16, everything that can block goes through std.Io: its concurrency (async against concurrent, futures, groups), its synchronization and cancellation model, the patterns for picking a backend and sizing its pools, an http client and server, and fibers, where LLVM drops a register clobber under ReleaseSafe.
memory and concurrency #
page_allocator hands out whole pages, so small allocations through it
cost a page each, and a protocol
reply whose slices all die together wants a
per-command arena. 0.17's default init.gpa
in Debug and ReleaseSafe is a SafeAllocator, whose buckets
stay resident while one allocation in
them lives. below std.Io sit atomics and memory ordering,
which are still the right tool for an
atomic rate meter read by many threads
and written by one.
building #
the build system notes come mostly from reading ghostty and bun:
basics (including why a build with no -Doptimize is
Debug), organization for large build.zig files,
dependencies, codegen,
cross-compilation, and
distribution.
around zig #
libraries and the C on the other side of them: sqlite
through zqlite and its transactions, websockets from
handshake to permessage-deflate, Lua embedded with
ziglua, and the
libuv handle lifetimes that hold
even when uv_spawn fails.
zig's issues moved from github to codeberg. numbers up to about 25xxx are on github, and 30xxx and later are on codeberg, where the API searches them:
curl -s "https://codeberg.org/api/v1/repos/ziglang/zig/issues?state=all&q=flate"
sources #
- pub-search, zql, pollz: sqlite, comptime sql
- zat, k256, atproto-bench: crypto, CBOR, firehose
- music-atmosphere-feed, find-bufo: bluesky services
- logfire-zig, prefect-server, zigman, zuvloop
- websocket.zig, a fork of karlseguin/websocket.zig; zqlite.zig
- ghostty and bun: build system