本文永久链接https://tonybai.com/2026/07/18/rust-to-zig-roc-compiler-rewrite

【导读】就在Bun官方发文炫耀“11天用AI把50万行Zig重写成Rust”没多久,另一个编程语言项目Roc甩出了一篇同样硬核的长文:过去一年半,他们把自己30万行的Rust编译器,一行一行重写成了Zig。没有AI代劳,纯人工死磕487天。更有意思的是,重写之后他们发现:丢掉Rust引以为傲的借用检查器,内存安全bug不但没暴增,反而比原来更少了。

文章要点:

  • Bun最近发文称用AI(Claude)花11天把50万行Zig代码重写成Rust;Roc编译器团队几乎同期发文,讲了一个方向相反的故事——487天纯人工把30万行Rust重写成Zig。
  • 重写的真正起因不是“嫌弃Rust”,而是一个叫“多态去函数化”的核心架构长期出bug,必须动大手术,团队顺势做了从零重写。
  • 选Zig而非继续用Rust,主要基于四点:构建速度更快、内存分配器控制更细粒度、生态更贴合编译器场景、以及Roc代码里unsafe占比过高(1200处/30万行),Zig能给不安全代码更多兜底。
  • 实测数据显示:丢掉借用检查器后,Zig版编译器的内存损坏bug总数(10个)反而少于原Rust版本(21个),且原Rust版本的bug全部出在“编译器生成的机器码”而非编译器自身逻辑,恰恰证明了借用检查器起了作用。
  • 增量编译速度上,Zig的-fincremental达到35毫秒,比同期优化后的Rust(3.4秒)快约100倍,代价是目前还得等下一个Zig稳定版才能稳定使用。
  • 团队还意外收获了“零解析反序列化”缓存机制:把数据结构设计成索引数组形式,磁盘数据可以直接原样读入内存使用,无需解析。
  • 结论并不是“Zig完胜Rust”:作者仍怀念Rust的自动测试内存管理、多态、私有字段、向后兼容性等特性,全文核心观点是“没有更好的语言,只有更适合具体工程约束的语言”。

大家好,我是Tony Bai。

程序员圈最近有点意思。

一边是JavaScript运行时Bun,官方博客宣布团队用Claude把整个Bun从Zig重写成了Rust,过程被形容成“闪电战”——大约50万行代码,11天搞定,AI是绝对主角。

另一边,函数式编程语言Roc的作者Richard Feldman(前端圈熟人,写过Elm的经典教材,现在是编辑器公司Zed的工程师)也在几乎同一时间发文,讲了一个方向完全相反的故事:Roc编译器原本是Rust写的,团队决定把它重写成Zig,而且是从零开始的scratch-rewrite,没有AI辅助的“直接搬运”,纯粹靠人工设计新架构。这一写,就是487天。

同样是语言迁移,一个11天,一个487天;一个是Zig奔向Rust,一个是Rust奔向Zig。放在一起看,比单独看任何一篇都有意思——它们其实回答了同一个问题的两面:Rust的安全性红利,到底是不是每个项目都需要的刚需?

Feldman自己也在文章里点出了这层对照,并且很诚恳地说:Bun那种“AI直接搬运”的打法,在Roc这里根本行不通,因为他们压根不是想做一次忠实的移植,而是借着重写的机会,把编译器里一个折磨了他们很久的架构问题彻底解决掉。

重写的真正导火索:不是嫌弃Rust,是架构撑不住了

很多人看到“从A语言重写到B语言”,第一反应是“肯定是A语言不好用”。但Roc团队的故事恰恰不是这样。

真正的导火索,是Roc语言里一个听起来很学术的特性:多态去函数化(polymorphic defunctionalization through lambda set specialization)。通俗点说,Roc想做到闭包捕获变量时不产生堆分配——这在大多数非系统级语言里都是要付出代价的,但Roc想两者兼得。这套机制一旦做对,能像内联优化一样,连带解锁一大批性能收益;可一旦做错,就是各种诡异到离谱的bug。

原来的Rust实现里,这类bug反反复复出现,团队一直没能连根拔起,直到贡献者Ayaz Hafiz用OCaml单独写了个原型,才验证出问题根源出在编译器好几个阶段的架构设计上——这意味着,要修好它,几乎等于要重写大半个编译器。

再加上当时已经有几位贡献者不约而同表示“我打算重写某个模块”,团队一合计:反正主要模块都要动大手术,不如干脆做一次彻底的从零重写,而不是缝缝补补的“忒修斯之船”式改造。

这一点其实也提醒我们:技术选型的讨论经常被简化成“哪个语言更好”,但很多真实世界的重写决策,起点根本不是语言之争,而是架构债务到了不得不还的地步。

摊牌时刻:为什么最终选了Zig,而不是继续用Rust?

决定重写之后,摆在团队面前的问题是:要不要还用Rust?

Feldman说得很实在:他自己很喜欢Rust,还开过Rust的教学课程,日常在Zed的工作里也天天写Rust。这不是一篇“骂Rust拉踩Zig”的文章。团队最终选Zig,是基于四个具体的工程考量,而且都是“踩过坑”之后的判断:

1. 构建时间。

Rust的cargo编译速度是长期痛点,即便是增量编译也慢,而且随着代码量增长越来越糟。团队判断换成Zig会明显更快。

2. 内存控制粒度。

编译器内部大量使用了不同的内存分配器,尤其是arena(区域内存管理)和struct-of-arrays布局。Rust生态几乎默认“全局只有一个分配器”这个假设,连专门做struct-of-arrays的库soa_rs也是如此;而Zig整个生态天生就是“分配器到处传递”的设计哲学,这正好是Roc编译器想要的写法。

3. 生态相关性。

两边生态里,真正跟编译器这种冷门需求相关的库其实都不多。但像“如何绕开LLVM C++库、更快地生成LLVM bitcode”这类刁钻需求,Zig那边反而现成的代码更多。

4. “不安全代码”的兜底能力。

这是最关键、也最反直觉的一条。Rust的设计假设是:内存不安全的代码应该被隔离在少量的unsafe块里,靠miri、Valgrind之类工具重点审查。

但对Roc编译器来说,unsafe从来都不是“少数情况”——重写前的Rust代码里,unsafe用了大约1200次(30万行代码里),对比一下,rustc自己350万行代码里也才4万次unsafe,比例上Roc反而更高。原因也很简单:编译器的工作本质就是生成机器码,处理内存不安全的操作是这份工作绕不开的一部分。 既然大量代码本来就要跟“不安全”打交道,那能不能有个语言,在处理这类代码时给的帮助更多——这正是团队看中Zig的地方。

硬核实测:丢了借用检查器,内存安全bug到底变多了没?

这是全文最有说服力的一段,也是最值得程序员认真读的一段。

团队直接用Claude Opus 4.8把Roc编译器issue追踪器里的历史bug全部标注了一遍,统计出这样一张表:

编译器中的bug类型 Rust版本 Zig版本
发生内存损坏的bug 21 10
未发生内存损坏的bug 2575 421
总计 2596 431

乍一看,Rust版本内存损坏bug的绝对数量(21个)竟然比Zig版本(10个)还多一倍还不止,是不是“不安全的Rust”又捅了篓子?

答案是否定的。 Feldman特意强调:这21个内存损坏bug,没有一个发生在编译器自身的逻辑里——这恰恰证明了Rust借用检查器起了作用。这些bug之所以存在,是因为编译器的本职工作就是生成机器指令,而这些指令跑起来出了问题,用户报的是“我的程序内存损坏了”,但根源永远在编译器生成的代码逻辑本身,不管是Rust版本还是Zig版本,概莫能外。

真正值得关注的是Zig版本那10个里的另外2个:两个use-after-free bug,都出在错误信息渲染上(文件名显示成了乱码问号方块),而且这两个问题,如果用Rust的借用检查器,是完全可以在编译期就被拦下来的。

团队还进一步做了个假设推演:如果当初选的是Rust,或者选的是Zig但开启运行时安全检查的ReleaseSafe模式,结果会怎样?

技术选择 实际影响
ZigReleaseFast(实际选择) 2个bug报告:部分报错信息文件名渲染失败
ZigReleaseSafe 2个bug报告:部分报错会直接panic、无法渲染
Rust借用检查器 以上两个bug都不会发生

18个月、数百个bug报告、几十万行代码走下来,Feldman的结论是:换成任何一个选项,对项目实际结果的影响都微乎其微。 他们赌的那一把,基本兑现了预期。

这里他也很公允地拉出了Bun的例子做对比:Bun那篇重写博客里提到,他们最头疼的是JavaScript垃圾回收对象和手动管理内存混在一起,内存泄漏和崩溃占了bug的相当大比例。而Roc编译器压根不跟JS或任何追踪式垃圾回收器打交道,所以这类问题在Roc这边根本不构成困扰。不同项目,真的有不同的真实需求,不是同一套结论可以直接套用的。

编译速度:35毫秒,还是3.4秒?

团队换Zig一个很朴素的诉求就是“编译快点”。结果如何?

好消息是:zig build --watch -fincremental增量编译一次代码改动,大约只要35毫秒,比预期还猛。

坏消息是:Zig当前的稳定版0.16.0有个bug,导致-fincremental在他们的代码库上直接失效,修复已经合并,但要用上必须切到还带着破坏性语言更新的0.17.0预发布版,团队权衡后决定还是等下一个稳定版。

作者给出了一份横向对比表(冷启动构建vs改动后的增量构建):

编译器版本 代码行数 冷启动构建 增量构建
原版·Rust 1.85.0 35.4万行 32.4秒 10.0秒
原版·Rust 1.97.0(最新稳定版) 35.4万行 25.4秒 3.4秒
重写版达到功能对等·Zig 0.16.0 32万行 39.6秒 8.6秒
重写版今天·Zig 0.17.0(尚未正式发布) 46.4万行 32.1秒 0.035秒

有意思的是,Rust编译器团队这一年半里也没闲着,同样一份Rust代码,从1.85升级到1.97,增量构建时间直接从10秒砍到3.4秒——如果Roc当年没换语言,光靠Rust官方的持续优化,也能省下三分之二的等待时间。Feldman对此毫不吝啬地送上了赞美。

但即便如此,Zig那35毫秒依然不是一个量级的胜利——是3.4秒的百分之一,而且这还是在代码量比对应的Rust版本多出约50%的前提下跑出来的成绩。他直言,自己还没听说Rust路线图上有什么能跟-fincremental相提并论的东西。

一个意外的架构红利:连“解析”这一步都能省掉

这部分是全文技术含量最高、也最容易被忽略的一块,但恰恰是Roc新缓存系统的核心创新。

团队用了一种源自Zig编译器本身、在游戏编程圈子里很常见的技巧:如果你把内存数据结构的布局,本来就设计成了“在现代硬件上跑得最快”的样子,那么这份内存数据其实可以直接从磁盘原样读回内存使用,不需要任何解析步骤

具体做法是:编译器所有的数据结构都用“32位索引代替指针”的方式来表示(而且经常是struct-of-arrays布局)。这样一来,数据结构可以直接原样写到磁盘上,不需要先序列化成别的格式;反过来加载时,只要把字节读进内存、做一些地址重定位,就能立刻可用,反序列化的速度约等于磁盘I/O速度,操作系统有缓存的话基本等同于一次memcpy。

这意味着,你连续跑两次roc check,第二次如果源码没变,所有解析、类型检查等中间产物会直接从磁盘“跳”进内存,快得几乎感觉不到。

但这套设计也有安全代价:用索引代替指针后,任何一个索引都可能“查错数组”,效果跟指针指向错误地址(比如use-after-free)是类似的——而这恰恰是Rust借用检查器从设计之初就没打算管的问题,借用检查器只管指针生命周期,不管“这个索引到底该配哪个数组”。

Rust生态里虽然有compact_arena这样的库尝试用宏在编译期生成类型标签来规避这个问题,但前提是你得提前知道要几个arena——Roc这种数量会随模块数变化的场景根本用不上,所以这个库自己都把创建入口标成了unsafe

Feldman对此有个挺有意思的吐槽:他并不认同把这种“创建一个空arena”的操作标成unsafe,因为它本身根本不会引发不安全——真正危险的操作,是后面“用索引查数组”这个动作,而这个动作在代码里几乎无处不在。

“把整个代码库都当成需要格外仔细审查的对象”不是什么好建议,“干脆放弃这种能大幅提升性能的技巧”同样不是好建议。当unsafe已经无法被当成稀有、可隔离的情况来对待时,一门“比Unsafe Rust更安全”的语言,自然会显得更有吸引力。

生态相关性:同一个Drop,在两个项目里两种命运

这一段特别适合放在一起对照着读。

Bun那篇重写文章里提到,Rust的Drop(类似析构函数,离开作用域自动清理)帮了他们大忙,能应对JS垃圾回收对象和手动管理内存混用的复杂场景。而Zig更推崇显式的defer,没有隐式的清理机制。

但Roc这边情况完全反过来:Drop恰恰是他们在Rust里的痛点,因为Rust生态默认假设大家都在用一个全局分配器、靠Drop做隐式释放,而Roc想做的几乎是相反的事——按模块、按编译阶段划分出一个个独立的arena。Zig生态里“分配器到处传递”的写法,反而正中下怀。

Feldman的原话总结得很到位:Rust的生态是为Bun那种写法优化的,Zig的生态是为Roc这种写法优化的。 没有谁更先进,只是适配对象不同。

另一处生态收获也很实在:LLVM是他们优化器的关键依赖,但LLVM频繁的破坏性API变更一直是升级噩梦。

好在LLVM有个稳定的序列化格式叫bitcode,只要自己写一个跟LLVM C++库解耦的bitcode序列化器,就能绕开升级的坑。

而据Feldman所知,野生环境里独立实现过这种序列化器的,只有Zig编译器自己——于是Roc新编译器干脆直接复用了这段Zig代码。他调侃说,2019年刚写下Roc第一行代码时,怎么也想不到多年后“这个项目最值钱的代码复用来源,会是一门当时还没听说过的语言写的编译器”。

换到Zig之后,他最想念Rust的6件事

这篇文章可贵的地方,是Feldman并没有把它写成一篇“Zig完胜”的软文。因为他人还在Zed天天写Rust,这些对比不是遥远的回忆,而是“当天早些时候”的真实体感:

  1. 测试里自动的内存分配与回收。Zig测试要精确写好init/defer deinit,一步错就因为内存泄漏挂掉测试;Rust完全不需要操心这些。
  2. 参数多态与特设多态(ad hoc polymorphism)。这两者跟Zig的comptime有重叠,所以Zig没有实现,但他确实怀念,比如Zig的分配器实现要接收一个anyopaque指针再强转,不像RustAllocator trait那样直接把self作为第一参数。
  3. 私有结构体字段。他理解Zig不设私有字段的理由,但在review代码diff时,只看到字段被直接访问,却看不到原始定义上“不建议直接访问”的文档提示,容易漏掉隐患。
  4. 全员统一的snake_case 命名风格,他偶尔会想念。
  5. unsafe和借用检查器本身带来的那种“安心感”。即便他清楚这背后是有代价的,也不认为Zig应该照搬,但那种“只需要在特定代码块里格外小心”的心理状态,他还是会怀念。
  6. Rust团队在向后兼容上的高水准。升级小版本几乎零成本,连edition升级都很顺畅;Zig目前阶段并不把向后兼容当成目标,这在预期之内,但也确实是他会想念的东西。

换到Zig之后,他意外爱上的东西

反过来,真正用下来之后,也有不少东西是他没预料到自己会这么喜欢的:

  • 没有宏。函数式编程一直讲究“做减法”,他发现自己对Zig的“减法哲学”同样受用。
  • comptime加普通函数,就能解决很多本来需要参数多态才能解决的问题。
  • 对数据布局的极致控制力。比如开箱即用支持非2的幂次的整数类型(像u7u5),不用自己手写位运算;还有packed struct、可以在调用处而非声明处内联函数——这些在Rust里通常得靠宏或额外的库才能做到。
  • Zig的构建工具链本身就非常出色,这也是为什么像Uber这样并不使用Zig语言本身的公司,也会用它的构建系统。团队用它顺利做出了面向Alpine Linux、WebAssembly的自包含静态二进制,即便还夹杂着把编译器内建标准库编译成二进制blob塞进最终可执行文件这种“骚操作”。
  • Zig的错误处理策略,尤其是把“堆分配失败”当成普通的用户态错误来处理这一点,他很喜欢。Roc语言本身的错误处理理念也类似(用带payload的匿名和类型来“自然积累”错误),比Rust里的anyhowthiserror或者原生Result更顺手。不过他也补充,自己还是更喜欢Rust那个后缀式的?操作符,而不是Zig的try关键字——这也是为什么Roc语言自己选择借鉴了Rust的这个设计。

小结:没有更好的语言,只有更合适的语言

把Bun和Roc这两篇重写复盘放在一起看,会发现一件很朴素的事实:它们几乎是同一套论据,推出了两个相反的结论,而且两边都没说错。

Bun面对的是JS垃圾回收对象和手动内存混用的复杂场景,Drop帮了大忙,Rust生态里现成的全局分配器假设也刚好贴合它的需求;Roc编译器面对的是大量本质上就“不安全”的机器码生成逻辑、arena式的内存管理需求,unsafe占比高到没法被当成例外情况处理,Zig的显式分配器传递和运行时安全检查反而更贴身。

Feldman全文最诚实的一句总结大概是这句:回头复盘18个月的实际数据,他很难说服自己“当初更需要投入更多工具去防止编译器自身的内存安全bug”——真正值得投入的,反而是给编译产物本身加更多内存安全保障,而这从来都不是借用检查器管辖的范围。

对普通开发者来说,这两篇文章合起来传递的信号其实很简单:“重写”从来不该是语言信仰之争的战场,它应该是架构债务和真实工程约束倒逼出来的选择。

下次再看到“某某项目从A语言重写成B语言”这种标题党新闻时,不妨先问一句:他们要解决的,到底是什么问题?

参考原文:Richard Feldman,《How Our Rust-to-Zig Rewrite is Going》,https://rtfeldman.com/rust-to-zig


还在为写 Agent 框架频频死循环、上下文爆炸而束手无策?我的新专栏 从0 开始构建 Agent Harness 将带你:

  • 抛弃臃肿框架,回归“驾驭工程 (Harness Engineering)”的第一性原理
  • 用 Go 语言手写 ReAct 循环、并发拦截与上下文压缩引擎等,复刻极简OpenClaw
  • 构建坚不可摧的 Safety Middleware 与飞书人工审批防线
  • 在底层实现 Token 成本审计、链路追踪与自动化跑分评估
  • 从“调包侠”进化为掌控大模型边界的“AI 操作系统架构师”

扫描下方二维码,开启从 0 开始构建Agent Harness 的实战之旅。


还在为“复制粘贴喂AI”而烦恼?我的新专栏 AI原生开发工作流实战 将带你:

  • 告别低效,重塑开发范式
  • 驾驭AI Agent(Claude Code),实现工作流自动化
  • 从“AI使用者”进化为规范驱动开发的“工作流指挥家”

扫描下方二维码,开启你的AI原生开发之旅。


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。