📖How Our Rust-to-Zig Rewrite is Going
Unlike Rust, C, and Zig, Roc is not a systems language; it has automatic memory management (using reference counting, both to avoid tracing collector pauses and also for Perceus optimizations and opportunistic mutation like Koka’s). Roc would have way more heap allocations if it needed one heap allocation per closure capture (like most non-systems languages do), but our closure captures don’t heap-allocate because Roc is the first non-academic language to implement polymorphic defunctionalization through lambda set specialization.
This might sound like a niche optimization, but in a functional language like Roc, defunctionalization turns out to be similar to inlining in that it unlocks a treasure trove of follow-up optimizations.
Compilers are unusual in that scratch-rewrites are the norm among successful projects. It’s often the only way to self-host, although not all compilers rewrite into their own language; see for example TypeScript’s rewrite to Go.
→ Rewriting from scratch is common for compilers
Zig has more features than Rust for making memory-unsafe code work correctly, and that was the area where we wanted the most help.
(This is my experience as well—heavily unsafe Rust is very cumbersome to rewrite and UB rules are very easy to violate accidentally.)
Rust’s unsafe has all the footguns but none of the checks. There’s a list of Rust projects that suffered memory safety issues, out-of-bound reads as well as use-after-free because of unsafe code.
LLVM has a stable bitcode format that you can pass to LLVM to avoid breaking changes in LLVM API. Zig compiler has an LLVM bitcode serializer and now Roc reuses it.