本文永久链接 – 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,这些对比不是遥远的回忆,而是“当天早些时候”的真实体感:
- 测试里自动的内存分配与回收。Zig测试要精确写好
init/defer deinit,一步错就因为内存泄漏挂掉测试;Rust完全不需要操心这些。 - 参数多态与特设多态(ad hoc polymorphism)。这两者跟Zig的
comptime有重叠,所以Zig没有实现,但他确实怀念,比如Zig的分配器实现要接收一个anyopaque指针再强转,不像RustAllocatortrait那样直接把self作为第一参数。 - 私有结构体字段。他理解Zig不设私有字段的理由,但在review代码diff时,只看到字段被直接访问,却看不到原始定义上“不建议直接访问”的文档提示,容易漏掉隐患。
- 全员统一的
snake_case命名风格,他偶尔会想念。 unsafe和借用检查器本身带来的那种“安心感”。即便他清楚这背后是有代价的,也不认为Zig应该照搬,但那种“只需要在特定代码块里格外小心”的心理状态,他还是会怀念。- Rust团队在向后兼容上的高水准。升级小版本几乎零成本,连edition升级都很顺畅;Zig目前阶段并不把向后兼容当成目标,这在预期之内,但也确实是他会想念的东西。
换到Zig之后,他意外爱上的东西
反过来,真正用下来之后,也有不少东西是他没预料到自己会这么喜欢的:
- 没有宏。函数式编程一直讲究“做减法”,他发现自己对Zig的“减法哲学”同样受用。
comptime加普通函数,就能解决很多本来需要参数多态才能解决的问题。- 对数据布局的极致控制力。比如开箱即用支持非2的幂次的整数类型(像
u7、u5),不用自己手写位运算;还有packed struct、可以在调用处而非声明处内联函数——这些在Rust里通常得靠宏或额外的库才能做到。 - Zig的构建工具链本身就非常出色,这也是为什么像Uber这样并不使用Zig语言本身的公司,也会用它的构建系统。团队用它顺利做出了面向Alpine Linux、WebAssembly的自包含静态二进制,即便还夹杂着把编译器内建标准库编译成二进制blob塞进最终可执行文件这种“骚操作”。
- Zig的错误处理策略,尤其是把“堆分配失败”当成普通的用户态错误来处理这一点,他很喜欢。Roc语言本身的错误处理理念也类似(用带payload的匿名和类型来“自然积累”错误),比Rust里的
anyhow、thiserror或者原生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原生开发之旅。

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