2026年7月15日,Roc编程语言的创始人Richard Feldman发布了一篇技术博客,宣布他们花了487天,把30万行Rust代码重写成了Zig。编译器的增量构建时间从3.4秒降到了35毫秒——快了整整100倍。
这不是一个孤立事件。在此之前,Gleam语言的编译器也走过了同一条路:从Rust迁到Zig。而另一边,Bun运行时则在2026年早些时候完成了反向操作——从Zig迁到Rust,只用了11天。
两拨顶尖的编译器开发者,在两个方向上做出了截然相反的选择。当一件事的正反两面都有人用真金白银的代码投了票,你就知道:这背后是一个天平问题。

35毫秒到底意味着什么?
3.4秒已经很快了。笔者用Rust写过不少项目,cargo check跑上两三秒、rust-analyzer在后台嗡嗡作响——说实话,习惯了也就习惯了。Rust的编译速度在过去18个月里进步神速,Rust 1.97相比1.85把增量构建砍掉了三分之二的时间。Feldman自己在文章里也由衷称赞了Rust团队的这份努力。
但35毫秒是另一个物种。
35毫秒意味着你按下Ctrl+S的瞬间——手指还没从键盘上抬起来——编译器已经告诉你结果了。这就是反馈回路消失。Zig团队的成员mlugg在Lobsters上描述这种体验时说了一个细节:他每次增量构建只花30毫秒左右,其中链接器的工作大约只占1毫秒。因为Zig的新ELF链接器被设计成以单个函数为粒度的增量链接——修改一个函数,编译器生成新的机器码,链接器直接在输出文件的.text段里找到旧代码的位置覆盖写入。没有syscall,因为输出文件是mmap到内存的。
这种「零等待」改变了开发者与编译器的关系。你不再需要攒一批修改再跑一次构建,而是可以在修改和反馈之间快速试探——像用解释型语言一样用编译型语言。Roc编译器的新版本甚至支持热代码加载:运行中的服务器可以在不改进程的情况下,自动切换到修改后的代码。这在Python世界里是标配,在编译型语言世界里,是奢侈品。
那么,Rust的类型安全到底「贵」在哪里?
如果35毫秒是Zig给的甜头,那Rust收的「安全税」到底有多贵?我们需要把这个问题拆开来看。
第一笔账:编译时间。 Rust的borrow checker在编译期做了一件极其奢侈的事情——它证明你的程序没有use-after-free、没有double-free、没有数据竞争。这个证明过程需要遍历整个程序的引用关系图,复杂度随着代码规模超线性增长。Rust的增量编译在不断进步,但borrow checker的本质决定了它不可能像Zig那样「秒出」。
第二笔账:架构自由度。 Roc编译器大量使用了arena分配器和struct-of-arrays布局——所有数据结构用32位索引替代指针,按字段拆成独立数组。这种风格在现代CPU上跑得飞快,还能直接mmap到磁盘实现「零解析反序列化」——第二次运行roc check的时候,所有已解析的数据结构直接从磁盘跳进内存,速度接近memcpy。
但问题是:这种编程风格几乎必然要和Rust的borrow checker打架。arena+索引的模式绕过了Rust的所有权系统,这意味着你的unsafe比例会远超典型Rust项目。Feldman的团队在30万行Rust代码中有约1200处unsafe——这比rustc编译器本身的unsafe密度高出一个数量级。当unsafe从「少数需要审计的角落」变成了「遍地的常态」,borrow checker提供的安全感就打了折扣。
两边的账本:数字会说谎,也会说真话
Feldman做了一件很诚实的事:他统计了编译器两个版本各自的内存损坏bug数量。
Rust版本:21个。Zig版本:10个。
初看,Zig赢了。但仔细拆开——Rust版本的21个内存损坏bug全部是miscompilation(编译器生成了错误的机器码),没有一个发生在编译器自身的逻辑中。borrow checker做对了它该做的事。Zig版本的10个中,8个也是miscompilation,剩下2个是use-after-free——都出在错误报告渲染文件名的地方,症状是错误消息里的文件名变成了乱码。
Feldman的结论平静得让人意外:「回顾18个月的开发、几百个bug报告、几十万行代码,我的主要感受是:选哪个都一样。」那2个use-after-free,Rust的borrow checker能拦住,Zig的ReleaseSafe模式能在运行时panic——但三种方案的实际影响都是「两个bug报告:某些错误消息不显示文件名」。
这个结论和Bun团队形成了微妙对照。Bun在从Zig迁到Rust时强调,对于需要同时管理JavaScript的GC值和手动管理内存的项目,use-after-free是「大量bug的来源」。Feldman完全同意这一点——然后指出,Roc的编译器不需要和JavaScript互操作。
关键不在于谁对谁错。关键在于:上下文决定一切。

社区的裂痕:这不是一场圣战
这篇文章在Lobsters上拿到了175分、62条评论,在Hacker News上也引发了激烈讨论。但最值得关注的双方都有来自一线的、有理有据的论据。
Rust核心团队成员Ralf Jung指出了Feldman引用的「rustc有4万处unsafe」数据的问题——这个数字包含了标准库、测试和注释中的出现次数,实际编译器中的unsafe远少于此。他同时承认:「我完全同意unsafe Rust难以写对,这是我非常担忧的问题。」
llogiq——compact_arena crate的作者——则指出Rust的类型标签系统可以在编译期区分不同arena的索引,避免「用错数组」的问题。但他也承认,这种技术在arena数量在编译期未知的场景下确实失效了。
aapoalas——一位自称「数据导向设计狂热者」的Rust用户——表达了典型的矛盾心态:「作为一个数据导向设计的狂热者和Rust重度用户,看到一个志同道合的项目离开Rust让人难过。」他随后列举了自己在Rust中实现类似优化的一系列尝试,语气里有一种「不甘心」的诚实。
笔者觉得,这种争论的健康之处在于:没有人说对方是傻子。没有人说「选Rust就是不懂性能」或者「选Zig就是不在乎安全」。大家都在承认这是一个真实的权衡——然后基于各自的项目上下文做出不同的选择。
更深一层的较量:comptime vs proc macro
在编译速度的数字游戏背后,还有一个更值得玩味的哲学分歧:用什么方式做编译期元编程?
Zig的选择是comptime——你写的就是普通的Zig代码,只是标记它在编译期执行。它像一个内置在编译器里的解释器,让泛型编程、代码生成、类型操作都变得和写运行时代码一样自然。没有第二种语法,没有token tree操作,没有卫生宏的怪癖。
Rust的选择是proc macro——一个独立的、在编译期执行的Rust程序,接收token流、操作token流、输出token流。它极其强大(理论上你可以做任何事),但也极其笨重。每个proc macro都是一个独立的crate,编译它本身就需要时间。Zig的comptime内嵌在同一个编译过程中,几乎没有额外开销。
这就是为什么Zig编译快的一个隐藏原因:它不需要先编译一套宏系统、再编译你的代码。元编程和主程序共享同一个编译器管线。Feldman在文章里说:「我喜欢Zig没有宏。」——这句话单独看像是吐槽,但结合上下文,它表达的是一种减法美学:少一种机制,少一层抽象,就少一份编译负担。
当然,减法就意味着失去。Feldman也承认他怀念Rust的trait系统和私有字段。这些都是Rust用加法换来的表达能力。选Zig,就是接受「简单」比「表达力」更重要——至少对于那些以编译速度为生命线的项目来说。
笔者的判断:鱼和熊掌,但你可以选盘子
这不是一篇站队的文章。笔者写完以上分析后的真实感受是:Rust和Zig的关系,正在从「谁更好」变成「谁更适合什么」。
如果你的项目像一个Web服务器或数据库——代码结构相对稳定,unsafe集中在少数热点,你更需要的是borrow checker给的长期信心——Rust仍然是当下最安全的选择。
如果你的项目像一个编译器——代码需要频繁迭代重构,unsafe遍布各处,编译速度直接影响你的思考节奏——Zig正在成为一个不能忽视的选项。因为在这类场景下,安全成本的定价不同。
Roc团队做的重新定义了他们需要的安全类型。他们的内存安全问题主要出在生成的机器码上,而不是编译器自身——而borrow checker根本管不到前者。当安全的瓶颈不在语言提供的保障范围内,为它支付编译时间的成本就变得可商榷了。
35毫秒和3.4秒的差距,本质上是两种开发哲学的具象化:一种相信机器能在编译期证明一切,一种相信开发者能在运行时管好一切。两者都不是完美的——但至少现在,开发者有了真正不同的选项。
这可能是系统编程近年来最好的消息。
参考链接:
- Richard Feldman:《How Our Rust-to-Zig Rewrite is Going》(2026年7月15日)
- Lobsters讨论:How Our Rust-to-Zig Rewrite is Going(175分/62评论)
- Bun团队:《Why We’re Rewriting Bun from Zig to Rust》(2026年)
- Zig官方开发日志:Incremental Compilation Demo
- Gleam语言FAQ:Why Rust for the compiler?