Roc Compiler Achieves Feature Parity in 300,000-Line Rust-to-Zig Scratch Rewrite
The Roc compiler team has completed a 487-day scratch rewrite of its codebase from Rust to Zig, reaching feature parity. The architectural transition resolves fundamental compiler-phase bugs in the language's specialization pipeline while enabling zero-allocation pattern matching and automatic hot code loading.
The Architectural Impetus: Lambda Set Specialization
Roc manages memory using reference counting combined with Perceus optimizations and opportunistic mutation to bypass tracing garbage collector overhead. To prevent heap allocations during closure captures, the language implements polymorphic defunctionalization via lambda set specialization. While this optimization unlocks downstream compiler optimizations similar to inlining, the original Rust implementation suffered from persistent, systemic bugs.
An OCaml prototype developed by Ayaz Hafiz revealed that these issues were deeply rooted across multiple compiler phases. Resolving these defects required a comprehensive redesign. Because several core contributors already planned to rewrite disparate components of the compiler, the team opted for a coordinated scratch-rewrite rather than a piecemeal refactoring approach.
Compiler Performance and Binary Footprint Reductions
Reaching feature parity allowed the team to compile Rocci Bird, a WASM-4 game under 1,000 lines of Roc code. Compiling the project using the new Zig-based compiler with the size optimization flag (`roc build --opt=size`) yields a 31KB WebAssembly binary. This represents a reduction of more than 50% compared to the original Rust compiler's output, which was more than double that size.
Additionally, the team compiled Roc's "echo" platform for browser execution. The updated compiler compiles to a 2.5MB WebAssembly binary that runs directly on the Roc homepage, enabling compilation and execution of basic Roc programs client-side.
Zero-Allocation Pattern Matching and Hot Code Loading
The Zig-based compiler introduces compile-time execution of pure functions to optimize runtime patterns. A primary example is pattern matching on interpolated string literals within HTTP request routing (such as matching a tuple of an HTTP verb and a path template like `"/users/${id}/${page}"`). Rather than parsing template strings at runtime, the compiler evaluates these patterns statically, ensuring complete type safety and achieving zero heap allocations.
For development workflows, the compiler now features automatic hot code loading. Running `roc server.roc` executes a local development server that dynamically swaps out code segments as source files change, routing subsequent HTTP requests through the newly updated code paths without an application restart. For production deployments, the compiler emits LLVM-optimized, self-contained static binaries. The cross-compilation pipeline (e.g., `roc build --target=x64musl`) guarantees deterministic, byte-for-byte identical output regardless of whether the build is executed on macOS or another host environment.
Rust to Zig: Methodology and Comparison
The 487-day rewrite represents a deep architectural redesign, contrasting with the Bun project's recent 11-day direct port of 500,000 lines of Zig to Rust. The Roc team previously utilized Zig to implement standard library primitives and selected it for the compiler rewrite over Rust, which remains the primary systems language for other systems like the Zed editor.
The team narrowed its language options to Rust and Zig due to internal expertise. The decision to completely transition the compiler to Zig was driven by the team's experiences with both toolchains and the specific requirements of the redesigned compiler architecture. The compiler project will not self-host, adhering to the design principle that the Roc compiler should remain written in an external systems language.