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: errdefer puts 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 cc can 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.