why I write Zig
Zig is the language I reach for when I want a Go-shaped operational profile, Rust-shaped compile-time honesty, and a stdlib that does not lie to me about IO.
2026-05-21 · 7 min read · chris olson
Zig is not on most teams' shortlist. It should be. This post is about what Zig does that the languages already on the shortlist do not, and what I am willing to give up to get it.
01 the shape of a Zig project
A Zig project is a directory with a build.zig, a build.zig.zon, and a src/. There is no Cargo.lock to fight with. There is no package-lock.json to regenerate when CI gets angry. There is no pyproject.toml declaring three different build backends. zig build is the build system, the test runner, the cross-compiler, and the package fetcher. It is one binary that you can scp to a fresh machine and the project compiles.
That sounds like a small thing. It is not. Build systems in most languages have grown large enough to deserve their own engineers. Zig folded the build system into the language and then kept going — build.zig is just a Zig file. You can @import("std") and walk a directory tree at configure time. The content pipeline that emits the static Project[] slice on this site is a Zig executable that runs as a build step. That is the whole pipeline. There is no separate generator language, no DSL, no shell script gluing tools together.
02 comptime is the feature
Most languages have macros. Zig does not. What Zig has is comptime — a keyword that runs ordinary Zig code at compile time and lets the result participate in types, dispatch, and codegen. The route parser in Verve is a comptime function that takes "/work/:slug" and returns a []const Segment. The runtime matcher walks the slice. There is no allocation at request time, no regex, no string scan beyond what was already done before the binary was linked. That is the kind of thing macros want to do; comptime is the same thing without a second language stapled to the first.
I have written macros in Rust and macros in Lisp. The Rust ones are powerful and impossible to read. The Lisp ones are powerful and impossible to predict. Zig's comptime is neither. It is just code. The reason it works is that there is no syntactic gap between the runtime language and the compile-time language. The same if, the same for, the same std.mem.eql. If you can write Zig, you can write comptime Zig.
03 the stdlib does not lie about IO
The single thing that has changed most about how I write systems code is std.Io. In Zig 0.16, IO is explicit. Reading a file takes an io: Io parameter. So does opening a directory, walking it, talking to a child process. Every blocking call in your stack carries the same handle. That handle is dispatchable — you can swap it for a non-blocking implementation, a test harness, or a deterministic clock.
In Go I have to remember which functions are safe to call from a goroutine. In Node I have to remember which functions return a Promise. In Rust I have to choose between tokio, async-std, and smol, and the choice ripples through every crate in the tree. In Zig, IO is a value. Library code does not pick. Application code picks.
It is one of those changes that sounds boring until you have lived with it for a quarter, and then you notice that the entire class of "what color is your function" arguments stopped applying to your codebase. Library code doesn't care. Test code doesn't care. The composability is the kind that you only get when the language designers said no to async sugar.
04 the bill on the way out
A Zig binary is a single file. A small server binary built -Doptimize=ReleaseFast is in the 1–3 MB range. On the deployment side, that means a Dockerfile that starts FROM scratch, copies the binary, and exposes a port. The image is the binary plus a thin shim. Cold start measures in milliseconds because there is nothing to warm up. There is no JIT. There is no GC. There is no Node runtime in a sidecar.
I will admit a soft spot for this part. Half of the cost of running a service is not the compute; it is the operational surface. Zig has the smallest operational surface of any language I have used in production. The number of moving parts you have to reason about between "code in repo" and "bytes hitting the kernel" is two: zig build, and the binary it produced. That is fewer parts than Go (which is already lean) by exactly one — Go's runtime is the part Zig does not have.
05 when not to reach for Zig
Zig is the wrong choice for the same reasons Rust is the wrong choice. If the service is a CRUD wrapper around Postgres that needs to ship by Friday, the bottleneck is Postgres and the language is going to be a tax on the team. Pick Go. If the team has not written systems code before, the manual memory management is a real cost. Pick Go. If the ecosystem you need lives in npm, you are going to have to bring it in through FFI, and that is a project of its own. Pick Node.
Zig is 0.16 today. The language is pre-1.0. Things still change. Library code from a year ago does not always compile against the current stdlib without edits. If your release cadence cannot absorb a quarterly migration, you should wait for 1.0 or stay where you are.
I keep writing Zig anyway, for a specific shape of project. Services that have to live for years. Libraries other people will vendor. Anything where the cost of a stale dependency tree is higher than the cost of compiling against master. Verve is the bet that those constraints describe a real category of work. The site you are reading is the bet that the category includes something as ordinary as a personal site.
06 the case I keep making
Zig is the smallest language I have shipped production code in that still does the things I need a production language to do. The grammar fits on a postcard. The stdlib fits in your head. The build system is the language. The IO model does not lie. The output is a single file.
You can talk yourself out of Zig by listing the things it does not have — generics that look like Rust's, ergonomic async, a million-package registry, a stable 1.0. None of those have stopped the codebase. What has surprised me is how much of "good production code" turned out to be about not having those things.
I will tell you when 1.0 ships, the way I will tell you that the next quarter's bill on a Zig service was lower than it would have been on the alternatives. Until then, I am writing Zig. If you have a long-lived service or a library that should outlive its author, you should look at it too.