Zig; what I think after months of using it
Zig’s Error Model and “Global” Error Type
- Confusion around “ErrorType is global”: clarified as a single, whole-program error set (within the link-time unit), not per-module.
- Errors are structurally typed: two
error{Foo}definitions in different libraries are equal; unlike Zig’s nominal structs/enums. - The compiler can track which error variants functions may return and whether callers handle all of them.
- Lack of payloads on errors is a pain point: associated data must be stored elsewhere (e.g., unions or out-parameters), reintroducing boilerplate and weakening inference.
Memory Safety, UB, and Zig vs Rust
- Long argument over the claim that Zig is “safer than unsafe Rust.”
- One side: all Zig code is effectively unsafe; Rust’s
unsafeis isolated and can be checked with tools like Miri, which catch more UB than Zig’s current checks. - Other side: writing correct unsafe Rust is harder because of subtle reference rules and noise; Zig’s simpler pointer model plus external checkers or sanitizers might be “safe enough.”
- Skepticism that a static checker can make Zig as safe as Rust without breaking existing code; current checker PoCs are incomplete.
Variable Shadowing and Immutability
- Heated debate: some see shadowing as a key feature, especially in Rust-style immutable code, to model “evolving” values while preventing reuse of old states.
- Critics argue it hides bugs and hurts readability; cite bad experiences in Go (
:=vs=). Some want it disabled via lints. - Defenders stress ergonomics: without shadowing, code accumulates many similar names and more opportunities to pick the wrong one; shadowing offers self-imposed guardrails.
Design Choices: Traits, Destructors, Strings, Generics
- No traits/typeclasses: framed as a deliberate choice for explicitness; critics call Zig’s “duck-typed” generics hard to reason about and poorly documented.
- No RAII-style destructors:
defer/errdeferare scope-based, not per-object; this makes some patterns (e.g., container of heap-owned values) clumsier but also exposes allocation strategy clearly. - No built-in string type: everything is
[]u8. Pro: forces explicit thinking about encoding and buffers; con: Unicode correctness and “character” semantics are pushed onto users, despite std unicode helpers.
Arbitrary-Size Integers and Bitfields
- Zig’s arbitrary-width integers are praised; Rust can emulate via crates but not at the language level.
- Debate on addressability of sub-byte values: C/C++ bitfields lack pointers; Zig introduces richer pointer types (including bit-packed) at the cost of complexity.
Rust vs Zig Adoption and Ergonomics
- Some claim Rust has effectively “won” as a C replacement; others point to Rust’s complexity, compile times, and maintenance/hiring costs.
- Views on Zig range from “niche but promising, especially for C interop and games” to doubt it will rival Rust without interfaces/closures and stronger safety story.
- Broader theme: Zig prioritizes simplicity and explicitness; Rust prioritizes stronger guarantees, even at ergonomic and conceptual cost.