How Our Rust-to-Zig Rewrite Is Going
Richard Feldman reports on 487 days of rewriting the Roc compiler from 300K lines of Rust into Zig, with real build timings, actual memory-safety outcomes, and a frank accounting of what each language provides.

The Rust-versus-Zig debate almost always happens in theory — this is 487 days of a real production compiler rewrite with actual bug tallies, actual build timings, and an honest account of what each language delivered for a specific problem shape.
Source
How Our Rust-to-Zig Rewrite is Going
By: Richard Feldman
Published: Jul 15 2026
Type: Experience report
Saved because it is one of the most data-grounded first-person accounts of switching systems languages at production scale, written by the creator of Roc who also uses Rust daily at Zed.
487 Days of Data on a Compiler Rewrite
The Roc compiler started in Rust, reached roughly 300,000 lines, and the team then rewrote it in Zig. Feldman is explicit that this was not a verdict on Rust — he uses it professionally every day at Zed. The decision came from specific mismatches between how the Roc compiler needed to be structured and what Rust’s ecosystem assumes.
The first mismatch was build times. At 300K lines of Rust, incremental builds took 10 seconds. Rust 1.97 has since improved that to 3.4 seconds for the same codebase — a genuine result of 18 months of Rust compiler investment. Zig’s -fincremental on 464K lines achieves the same in 35 milliseconds. That is not a marginal improvement; it is a different performance category for someone making small edits and re-running tests in a tight loop.
The second mismatch was allocator conventions. Roc’s compiler uses arenas throughout, different allocators for different compilation phases, and struct-of-arrays layouts everywhere. Rust’s ecosystem is built around a single global allocator — Drop, standard collections, and nearly every published crate make that assumption. Zig’s ecosystem passes allocators as explicit parameters because that is idiomatic Zig. Off-the-shelf Zig code fits Roc’s memory model; off-the-shelf Rust code fights it. The team estimated roughly 1,200 uses of unsafe in 300K lines to get the control they needed — enough that Rust’s “unsafe is rare and audited” assumption stopped holding.
A direct consequence of the struct-of-arrays discipline is zero-parse deserialization. All compiler data structures use 32-bit indices over arrays rather than pointers. Because of this layout, the structures can be written directly to disk and loaded back without any parsing step — deserialization speed is bounded only by I/O, or by memcpy if the OS has the bytes cached already. Running roc check twice on unchanged source loads all parsed and type-checked state at memory-copy speed. The technique depends entirely on the index-over-pointer discipline, which is natural in Zig and requires fighting the standard library in Rust.
The memory safety picture in practice was less decisive than theory suggests. Over 487 days and hundreds of bug reports on the Zig-based compiler, 10 involved memory corruption. Eight were miscompilations — the compiler emitted incorrect machine code, which caused corruption in compiled programs. Miscompilations are outside the scope of any borrow checker regardless of language. The remaining two were use-after-free bugs in error reporting, rendering filenames as replacement characters in error messages. Rust’s borrow checker would have caught both. After 18 months and 450K lines of code, the entire borrow-checker-preventable impact was two bugs that garbled some error output.
What Feldman misses from Rust: private struct fields — Zig has none, so a field access in a code review gives no signal about intended encapsulation. He also misses automatic allocation and deallocation in tests without explicit defer deinit, Rust’s backwards-compatible release process, and the psychological containment of unsafe blocks — knowing certain problem classes stay confined to explicitly marked regions. That feeling is coherent even while not wanting the costs associated with it for this project.
What he values in Zig: comptime handles generics and much of what macros solve in Rust without a separate macro system; packed struct support and non-power-of-two integer types are built in; the build toolchain is first-class infrastructure; and failed heap allocations are ordinary errors rather than process panics. The Zig ecosystem also had the one off-the-shelf dependency that mattered most: an LLVM bitcode serializer in the Zig compiler itself, which lets Roc bypass LLVM’s breaking C++ API changes entirely — a dependency that simply did not exist in Rust.
What I want to remember
Remember: Language choice is a project-shape problem — match the language’s memory model and ecosystem assumptions to your actual architecture, not its general safety reputation to your general concerns.