Mihalis Tsoukalos loves C, but prefers Zig for new systems code. His comparison focuses on the mistakes and maintenance work he encounters, with examples compiled on a Mac.
- Cleanup:
errdeferputs error-path cleanup next to each allocation. A failing allocator makes it possible to test successive allocation failures. - Bounds and errors: Slices carry their length, and debug and safe builds check bounds. Errors are part of function return types and must be handled or propagated.
- Compile-time work: Zig uses language-level compile-time code for generics and platform branches instead of textual preprocessor macros.
- C interoperability:
zig cccan cross-compile C, and a Zig build can include C and Zig files together.
The tradeoffs matter. Zig does not prevent use-after-free or dangling pointers, and fast and small builds disable most safety checks. C also offers greater stability, a larger ecosystem, and support for hardware that Zig may not target.
The practical question is where a team’s time goes: managing failure paths and undefined behavior in C, or absorbing the churn of a younger language while staying careful about memory ownership.