Roc 编译器重写:从 Rust 到 Zig 的进展与经验教训

Roc 编译器重写:从 Rust 到 Zig 的进展与经验教训

功能等价里程碑

全新的基于 Zig 的 Roc 编译器现在在功能上已经与原始的 Rust 编译器保持一致。

  • 该里程碑使得 Rocci Bird WASM‑4 游戏(不足 1 K 行 Roc 代码)能够使用 roc build --opt=size 编译成 31 KB 的 Wasm 二进制文件,仅为 Rust 版本的一半大小。
  • 这一成就是今年稍后计划发布的 v0.1.0 的预发布步骤。
  • 贡献者包括 Anthony Bullard、Sam Mohr、Jared Ramirez、Ayaz Hafiz、Aurélien Geron、Stephan、Niclas Åhdén、JRI98、Jasper Woudenberg,以及主要贡献者 Anton‑4 和 Luke Boswell。

"看到那些块状的紫色像素时,我忍不住笑了出来,终于做到这一步了!" – Joran Greef


热代码加载与跨编译

Roc 现在支持开发期间的热代码重载以及确定性的跨平台编译。

  • 运行 roc server.roc 会启动一个 Web 服务器;编辑源码后服务器会实时更新,无需重启。
  • roc build --target=x64musl 能在 macOS、Linux 或任何平台上生成完全相同的二进制文件,这是并非所有编译器都能保证的。

带字符串插值的模式匹配

字符串插值可以直接在模式匹配中使用,从而产生零分配的路由代码。

match (verb, path) {
  ("GET", "/users/${id}/${page}") => match page {
    "" | "profile" => ok(id)
    "settings" => ok(with_default(user_agent, id))
    "posts/${post_id}" => ok("Post ID: ${post_id}")
    _ => not_found
  }
  ("GET", "/users/${id}") => ok(id)
  ("POST", "/posts/new") => created(with_default(...))
  _ => not_found
}
  • 路由逻辑 不进行任何堆分配,并且在编译时即保证类型安全。
  • 实时演示可在 roc‑lang.org 首页通过浏览器内的 WebAssembly 编译器体验。

为什么要从头重写?

Roc 需要一次根本性的架构改造,而不是简单的移植。

  • 原编译器的 lambda‑set 特化(多态去函数化)存在缺陷,必须在多个阶段重新设计。
  • 完全重写避免了“忒修斯之船”式的增量重构,并且符合编译器项目常见的从零开始重写的做法。

为什么选 Zig 而不是 Rust?

四个实际考量驱动了这一决定:

  1. 构建时间 – Zig 的增量构建承诺显著加快反馈循环。
  2. 内存控制 – Zig 以分配器为中心的生态与 Roc 基于 arena 的设计相匹配,而 Rust 假设全局分配器。
  3. 生态相关性 – 所需的低层 LLVM bitcode 工具在 Zig 中已有实现,而 Rust 中缺乏。
  4. 内存不安全辅助 – Zig 提供更细粒度的 unsafe 代码检查(如 ReleaseSafe),适合大量使用 unsafe 部分的项目。

没有 Borrow‑Checking 的生活

内存安全统计与实际观察:

  • 2019 年微软的一项研究显示,约 70 % 的年度安全补丁是针对内存安全缺陷。
  • Zig 的 ReleaseSafe 在运行时捕获 use‑after‑free;ReleaseFast 在生产环境下跳过检查。
  • Rust 的 borrow checker 消除了大量 unsafe bug,但在 Roc 30 万行代码中仍需要 ≈1 200 个 unsafe 块(约占代码的 0.4 %)。
  • 实际上,两种语言在编译器生成错误代码时都可能导致内存损坏 bug。

重写后的内存安全性

Bug 报告分析(由 Claude Opus 4.8 分类):

Bug 类型 Rust 编译器 Zig 编译器
内存损坏 bug 21 10
非损坏 bug 2 575 421
总计 2 596 431
  • 所有 21 条 Rust 损坏 bug 均为 误编译(生成的程序导致内存损坏),而非编译器自身逻辑错误。
  • 在 10 条 Zig 损坏 bug 中,8 为误编译;2 为真实的编译器 bug(错误报告代码中的 use‑after‑free),这些在 Rust 的 borrow checker 下本会被阻止。
  • 假设的替代方案:
    • Zig ReleaseSafe:同样的两条 bug 会触发 panic。
    • Rust:会在编译期捕获这两条 bug。
  • 结论:对 Roc 的使用场景而言,实际安全差异并不大。

构建时间对比

编译器 行数 冷构建 增量重建
Rust 1.85(原版) 354 K 32.4 s 10.0 s
Rust 1.97(当前) 354 K 25.4 s 3.4 s
Zig 0.16(功能等价) 320 K 39.6 s 8.6 s
Zig 0.17(今日) 464 K 32.1 s 0.035 s
  • Zig 在当前代码库上的增量重建仅 35 ms,比 Rust 的 3.4 s 快 100 倍以上,即使 Rust 经过 18 个月的改进仍远不及 Zig 的增量速度。
  • Zig 0.16 中的一个 bug 破坏了 -fincremental,已在即将发布的 0.17 版中修复。
  • Rust 的构建时间提升(降低至三分之二)虽令人印象深刻,但仍远落后于 Zig 的增量速度。

零解析反序列化(内存控制)

Roc 将编译器数据结构直接缓存到磁盘,并在无需解析的情况下重新加载。

  • 数据结构使用 32 位索引结构体数组(structure‑of‑arrays) 布局,能够直接进行二进制转储。
  • 重新加载本质上是一次 memcpy 速度的操作,仅受磁盘 I/O 或 OS 缓存限制。
  • 该技术依赖于 Zig 对细粒度分配器的管理能力;Rust 的全局分配器模型使得在不大量使用 unsafe 的情况下保证安全更加困难。

生态适配性

  • Zig 提供即用的 LLVM bitcode 生成器和以分配器为中心的库,能够直接映射到 Roc 的需求上。
  • Rust 的生态假设全局分配器和基于 Drop 的清理,这与 Roc 的 arena 设计相冲突。
  • 对 Roc 最有价值的可复用组件是 Zig 自身的编译器,它已经实现了所需的 LLVM bitcode 序列化功能。

从 Rust 中失去的东西

  • 自动测试分配器清理 – Rust 的测试会自动检测泄漏,无需手动 defer/init 代码。
  • 参数化与临时多态 – Rust 的泛型系统比 Zig 基于 comptime 的方式更为丰富。
  • 私有结构体字段 – Rust 的可见性修饰符在编译期提供了防止意外字段访问的安全性。
  • 一致的向后兼容性 – Rust 的稳定发布政策让升级毫不费力;而 Zig 仍处于 1.0 前,破坏性改动在所难免。

对 Zig 的喜爱之处

  • 没有宏 – 语言表面更简洁。
  • 细粒度的数据布局控制 – 直接支持非二次幂整数类型(u7u5)和紧凑结构体。
  • 强大的构建系统 – 单命令即可跨编译到 Alpine、WebAssembly 以及静态二进制。
  • 错误处理 – 显式的 try/catch 风格与 Roc 的 sum‑type 错误模型相契合。
  • 以分配器为中心的 API – 与 Roc 设计匹配,无需额外的变通方案。

Roc 的下一步计划

  • 今年稍后发布 v0.1.0(首个带编号的正式版)。
  • 正在进行的工作包括文档编写、标准库打磨以及扩展 Roc Exercism 练习。
  • 项目由 Roc Programming Language Foundation(501(c)(3) 非营利组织)以及多家企业赞助商支持。

社区反馈(精选 HN 评论)

"Zig 的增量构建是个杀手级特性。短期内我能理解你们为何切换,但我们能指望 Rust 赶上吗?"onlyrealcuzzo

"我不认为内存不安全代码在生成机器码时占比大;更多是运行时的问题。"steveklabnik

"Zig 的 ReleaseSafe 并没有真正文档化 use‑after‑free 检查,这个说法有点夸大。"landr0id

"构建时间数据很惊人,但比较的是成熟的 Rust 代码库和全新的 Zig 代码库。"dminik

"Zig 仍在 1.0 前,而 Rust 已经是 1.0 之后,这本身就影响了很多开发者的选择。"dev_l1x_be


底线

Roc 的重写表明 Zig 能够提供极快的增量构建和细粒度的内存控制,同时在内存安全性上与原始的 Rust 编译器相当(甚至在某些情况下更好)。权衡在于生态尚不成熟、偶有破坏性改动,以及失去了一些 Rust 的便利特性(如自动测试分配器清理和更丰富的泛型抽象)。总体而言,项目的进展验证了在高度依赖自定义分配器、零解析反序列化和热代码重载的编译器场景下,采用 Zig 是一个合理的决定。

Sources