Roc Compiler Rewrite: Feature Parity Achieved After 18‑Month Rust‑to‑Zig Migration

Roc Compiler Rewrite: Feature Parity Achieved After 18‑Month Rust‑to‑Zig Migration

Feature Parity Milestone

The Roc compiler rewrite from Rust to Zig has reached feature parity with the original Rust implementation, enabling the Roc‑WASM‑4 game Rocci Bird to compile to a 31 KB WebAssembly binary—less than half the size produced by the Rust compiler. This milestone is a prerequisite for the upcoming 0.1.0 release, though it is not yet a formal version.

Hot Code Loading and Cross‑Compilation

Roc’s new compiler supports hot code loading: running roc server.roc and editing the source automatically updates the running server on the next request. The same workflow works for a 2D game demo. For production, roc build produces an LLVM‑optimized, self‑contained binary. Cross‑compilation is as simple as roc build --target=x64musl, yielding identical output on any host platform.

Zero‑Allocation Pattern Matching with String Interpolation

Roc now allows string interpolation inside pattern matches, e.g.:

match (verb, path) {
  ("GET", "/users/${id}/${page}") => ...
}

This feature is type‑safe at compile time and performs zero heap allocations, a stark contrast to typical interpreted languages where each request would allocate memory.

“If you’re interested in the technical details of how we got HTTP routing down to zero allocations, let me know on Roc Zulip.”

Why a Scratch Rewrite?

The original compiler suffered from deep architectural bugs in its lambda‑set resolution system. A prototype in OCaml demonstrated that fixing these bugs required a wholesale redesign across multiple compiler phases. Rather than incrementally refactor (the "Ship of Theseus" approach), the team opted for a full rewrite, a common practice among successful compilers.

Choosing Zig Over Rust

The team evaluated Rust and Zig based on four criteria:

  1. Build Times – Cargo builds were becoming a bottleneck; Zig promised dramatically faster incremental builds.
  2. Memory Control – Zig’s allocator‑centric ecosystem matches Roc’s arena‑based memory model, whereas Rust assumes a global allocator.
  3. Ecosystem Relevance – Few Rust crates addressed Roc’s niche needs (e.g., fast LLVM bitcode emission), while Zig already provided suitable implementations.
  4. Memory‑Unsafety Assistance – Roc’s compiler requires extensive unsafe code; Zig’s ReleaseSafe and ReleaseFast modes offer runtime checks that complement this usage.

Life Without Borrow‑Checking

Memory‑safety statistics show that 83.6 % of 2018 vulnerabilities are unrelated to language safety mechanisms, while 16.4 % are use‑after‑free (UAF) errors. Zig’s ReleaseSafe mode catches UAFs at runtime, whereas Rust’s borrow checker prevents them at compile time. In practice, Roc’s Zig rewrite recorded 10 memory‑corruption bugs (2 of which were compiler‑internal UAFs that Rust would have caught) versus 21 such bugs in the Rust version—most arising from miscompilations rather than the compiler’s own memory handling.

Bug type Rust Zig
Memory corruption 21 10
Non‑corruption bugs 2,575 421
Total 2,596 431

If the Zig compiler were built with ReleaseSafe, the two internal UAF bugs would become panics rather than silent errors, but the overall safety impact would remain minimal.

Build‑Time Comparison

Incremental rebuilds on a typical Intel desktop show a stark contrast:

Compiler LoC Cold Build Incremental
Rust 1.85 (original) 354 K 32.4 s 10.0 s
Rust 1.97 (original) 354 K 25.4 s 3.4 s
Zig 0.16 (feature parity) 320 K 39.6 s 8.6 s
Zig 0.17 (today) 464 K 32.1 s 0.035 s

Even after accounting for a bug in Zig 0.16 that broke -fincremental, the 35 ms incremental rebuild on Zig 0.17 is 100× faster than Rust 1.97’s 3.4 s, despite a 50 % larger code base.

Zero‑Parse Deserialization for On‑Disk Caching

Roc’s compiler stores its internal data structures as arrays of 32‑bit indices (structure‑of‑arrays layout). Because these structures contain no pointers, they can be written directly to disk and re‑loaded without any parsing—simply memcpy‑speed deserialization. This technique, inspired by Zig’s compiler, yields near‑instant incremental checks when source files are unchanged.

Ecosystem Fit

Rust’s ecosystem assumes a global allocator and pervasive Drop semantics, which conflicted with Roc’s arena‑based memory model. Zig’s design of passing explicit allocators aligns perfectly with Roc’s needs. Moreover, Zig’s compiler provides a stable LLVM bitcode serializer, enabling Roc to avoid breaking API changes in LLVM’s C++ interface.

Things Missed from Rust

  • Automatic allocation/deallocation in tests, which reduces boilerplate.
  • Private struct fields that enforce encapsulation at compile time.
  • Consistent snake_case naming.
  • The borrow‑checker’s focused safety guarantees, which would have caught the two internal UAF bugs.
  • Seamless backwards‑compatible upgrades; Zig’s pre‑1.0 status means breaking changes are more common.

Things Appreciated About Zig

  • No macro system, reducing hidden complexity.
  • Fine‑grained control over data layout (e.g., u7, packed structs).
  • Powerful build toolchain that produces self‑contained binaries for Alpine Linux and WebAssembly.
  • Explicit error handling (try/catch) and treating allocation failures as regular errors.
  • An ecosystem of compiler‑specific utilities (e.g., LLVM bitcode emission) unavailable in Rust.

Outlook

The team plans to ship Roc 0.1.0 later this year, marking the language’s first numbered release. Nightly builds are already available for early adopters, though they contain bugs and incomplete documentation. Ongoing work includes expanding documentation, stabilizing the build pipeline, and continuing to refine the compiler’s performance and ergonomics.


Community Highlights

“Zig’s incremental builds are DEFINITELY a killer feature.” – commenter

“I think the post would be more compelling with a scientific language‑selection study.” – commenter

“The 35 ms incremental rebuild sold me; I’m curious about ARM performance.” – commenter

These reactions underscore the community’s enthusiasm for Zig’s speed and the desire for more empirical data on language‑choice decisions.

Sources