Background
I came to Zig from a mix of C (day-to-day embedded work) and Rust (side projects, CLI tools). Zig occupies an interesting middle ground: it aims to be a better C without the complexity of Rust’s borrow checker.
What I like immediately
Comptime
comptime is Zig’s answer to C templates, Rust generics, and macro systems — but without a separate metalanguage.
Any expression you can write at runtime, you can also evaluate at compile time:
const std = @import("std");
fn maxOf(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
pub fn main() void {
const x = maxOf(i32, 10, 42);
std.debug.print("{d}\n", .{x});
}
This eliminates a whole class of C macro abuse while staying readable.
Explicit allocators
Every allocation is explicit. There is no hidden heap; you pass an allocator to anything that needs memory:
const allocator = std.heap.page_allocator;
const list = try std.ArrayList(u8).init(allocator);
defer list.deinit();
Compared to Go (implicit GC) or C (implicit malloc), this is more verbose — but you always know where memory comes from.
Error handling
Zig uses error unions instead of exceptions or errno:
fn readFile(path: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return try file.readToEndAlloc(std.heap.page_allocator, 1 << 20);
}
The ! prefix means “this returns T or an error.”
try unwraps or propagates.
It reads almost as naturally as Go’s if err != nil, but the type system enforces it.
The sharp edges
- No closures — this trips me up coming from Go/Rust.
undefinedbehaviour — less than C, but still present in release builds.- Young tooling — the LSP (ZLS) is great but occasionally loses type inference on complex comptime paths.
- Package manager is new (Zig 0.12+) — ecosystem is still growing.
vs. C
Zig can call C directly (@cImport, @cInclude) with zero overhead.
For replacing individual C files in a mixed project, this is compelling.
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf("hello from C via Zig\n");
}
Verdict
Zig is not ready to replace Rust for safety-critical work, and not as mature as C for legacy embedded targets. But for new systems code where you want C-level control with better ergonomics and a sane build system, it’s worth the investment.
I’ll be writing more as I build a small bytecode VM in Zig — stay tuned.