内容提要
Roc编译器团队耗时487天,将30万行Rust代码人工重写为Zig,未使用AI辅助。重写原因包括构建速度、内存控制、生态适配及unsafe代码占比过高。重写后内存损坏bug从21个降至10个,增量编译速度提升约100倍。文章强调语言选择无绝对优劣,取决于具体工程需求。
延伸解读
重写决策的根源:架构债务而非语言优劣
Roc团队重写编译器并非因为Rust不好,而是核心架构“多态去函数化”长期存在难以根除的bug,修复几乎等于重写大半个编译器。这提醒我们,许多重写决策的起点是架构债务,而非语言之争。技术选型讨论常被简化为“哪个语言更好”,但真实工程中,架构约束往往才是决定性因素。
内存安全bug的对比:借用检查器的真实作用
数据显示,Zig版本的内存损坏bug(10个)少于Rust版本(21个),但Rust版本的bug全部发生在编译器生成的机器码中,而非编译器自身逻辑,这恰恰证明了借用检查器的有效性。Zig版本中2个use-after-free bug本可被Rust借用检查器在编译期拦截。因此,语言的安全特性需结合具体场景评估,不能仅凭bug数量下结论。
性能提升的代价:增量编译速度与稳定性权衡
Zig的增量编译速度达到35毫秒,比Rust的3.4秒快约100倍,但这一优势依赖尚未发布的Zig 0.17.0稳定版,当前稳定版存在bug导致该功能失效。团队选择等待稳定版而非使用预发布版,体现了对稳定性的重视。性能提升往往伴随工具链成熟度的权衡,实际采用需谨慎评估。
生态适配的差异:同一特性在不同项目中的命运
Rust的Drop机制在Bun项目中是优势,但在Roc中却是痛点,因为Roc需要按模块划分独立arena,而Rust生态默认全局分配器。Zig的“分配器传递”哲学更契合Roc需求。这显示语言生态的适配性因项目而异,没有绝对优劣,选择应基于具体工程约束。
Q&A
Roc编译器团队为什么决定将Rust代码重写为Zig?
Roc编译器团队决定重写是因为核心架构问题“多态去函数化”长期存在bug,必须进行大手术。他们选择Zig而非继续使用Rust,主要基于四个原因:构建速度更快、内存分配器控制更细粒度、生态更贴合编译器场景、以及Roc代码中unsafe占比过高(1200处/30万行),Zig能给不安全代码更多兜底。
Roc编译器从Rust重写为Zig后,内存安全bug的数量有何变化?
重写后,Zig版本的内存损坏bug总数为10个,少于原Rust版本的21个。但Feldman强调,Rust版本的21个bug全部发生在编译器生成的机器码中,而非编译器自身逻辑,这恰恰证明了借用检查器的作用。Zig版本的10个bug中有2个是use-after-free,出现在错误信息渲染上,如果用Rust的借用检查器,这些bug可以在编译期被拦截。
Roc编译器重写为Zig后,增量编译速度提升了多少?
重写后,Zig的-fincremental增量编译速度达到35毫秒,而同期优化后的Rust版本为3.4秒,快了约100倍。但需要注意的是,这个速度是在代码量比对应Rust版本多约50%的前提下实现的,且目前需要等待Zig下一个稳定版才能稳定使用。
Roc编译器团队在重写过程中意外获得了什么架构红利?
团队意外获得了“零解析反序列化”缓存机制:通过将数据结构设计成索引数组形式(用32位索引代替指针),磁盘数据可以直接原样读入内存使用,无需解析。这大大加快了重复运行时的速度,但同时也带来了安全代价,因为索引可能查错数组,而Rust借用检查器无法检测这类问题。
Roc编译器团队为什么认为Zig比Rust更适合他们的项目?
团队认为Zig更适合是因为:1) 构建速度更快;2) 内存分配器控制更细粒度,符合编译器大量使用arena和struct-of-arrays的需求;3) 生态更贴合编译器场景,如LLVM bitcode序列化器;4) 对于大量本质不安全的代码,Zig提供了更多兜底能力,如运行时安全检查。
Roc编译器团队在重写后最怀念Rust的哪些特性?
团队最怀念Rust的六个方面:1) 测试中自动的内存分配与回收;2) 参数多态与特设多态;3) 私有结构体字段;4) 统一的snake_case命名风格;5) unsafe和借用检查器带来的安心感;6) Rust团队在向后兼容上的高水准。
Roc编译器团队在换用Zig后意外喜欢上了哪些特性?
团队意外喜欢上的特性包括:1) 没有宏,符合函数式编程的减法哲学;2) comptime加普通函数能解决很多问题;3) 对数据布局的极致控制力,如支持非2的幂次整数类型;4) Zig的构建工具链非常出色;5) 错误处理策略,尤其是将堆分配失败当作普通用户态错误处理。
Roc编译器团队对Bun用AI将Zig重写为Rust有何看法?
Feldman指出,Bun的AI直接搬运打法在Roc这里行不通,因为Roc不是做忠实的移植,而是借重写解决架构问题。同时,Bun面对的是JS垃圾回收对象和手动内存混用的场景,Rust的Drop和全局分配器假设适合它;而Roc编译器不涉及JS,且需要arena式内存管理,所以Zig更合适。这体现了不同项目有不同需求。