Rust到Zig重写项目进展如何
速览
该文章介绍了一个团队将代码库从Rust语言重写为Zig语言的过程,包括重写动机、面临的挑战以及当前进度。这反映了开发者社区对新兴语言Zig的关注,但并非AI领域事件。
AI 深度解读
背景
Roc 语言的编译器团队在过去一年半的时间里,一直在将原有的 30 万行 Rust 代码重写为 Zig。最近他们达到了一个重要的里程碑:新编译器在功能上与原始编译器持平。由于 Bun 项目此前分享了他们反向(从 Zig 到 Rust)重写的经验报告,所以现在正是回顾 Roc 从 Rust 转向 Zig 进展的好时机。
核心内容
功能持平里程碑
达到这一里程碑后,团队得以更新 Brendan Hansknecht 制作的 2024 年 WASM-4 游戏 Rocci Bird(美术由 Luke DeVault 提供),使其使用新编译器。该游戏全部代码不到 1000 行 Roc 代码,可在 itch.io 或通过 WebAssembly 直接运行。更新后的源代码比原版更简洁,roc build --opt=size 现在输出 31KB 的 wasm 二进制文件(原始编译器生成的文件大小超过两倍)。虽然 Rocci Bird 不是大型代码库,但要让它运行,新编译器必须实现大量功能。
致谢
团队感谢了多位贡献者,包括 Anthony Bullard 和 Sam Mohr(新解析器)、Jared Ramirez(新类型检查器)、Ayaz Hafiz(新 lambda set 解析系统,以及大量原始编译器工作)、Aurélien Geron(更新了 108 个初学者练习)、Stephan(让编译器的新 "echo" 平台在浏览器中运行)、Niclas Åhdén(Roc 最高产的生产用户,耐心提交 bug 报告)、JRI98(系统化复现和调查 fuzzer 错误)、Jasper Woudenberg(迭代 API 设计)等。特别感谢 Anton-4 和 Luke Boswell 在新编译器中的巨大贡献。
重写时间对比
这次重写耗时 487 天,而 Bun 将其约 50 万行 Zig 代码重写为 Rust 只用了 11 天。差异的原因与 Rust 或 Zig 本身无关,主要在于 Bun 是直接移植,而 Roc 团队决定重写是因为他们打算进行大量架构变更,Bun 所用的方法并不适用于他们的场景。
新功能
热代码加载与交叉编译:Roc 的新编译器在开发期间自动进行热代码加载。例如,运行 roc server.roc 启动一个 Web 服务器,然后修改代码,下次服务器处理请求时会自动使用新代码。部署时,roc build server.roc 生成 LLVM 优化过的自包含二进制文件。Roc 还支持交叉编译,例如 roc build --target=x64musl 可在任何系统上生成相同的输出字节。
模式匹配中的字符串插值:HTTP 请求处理逻辑示例:
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
}
"/users/${id}" 语法不是在运行时解析模板字符串,而是新语言特性:模式匹配内部的字符串插值。这不仅在编译时类型安全,而且整个代码片段零堆分配。
为什么从零开始重写
Roc 不是系统语言,它使用自动内存管理(引用计数,避免追踪式收集器暂停,并实现 Perceus 优化和机会性突变,类似 Koka)。Roc 通过多态去函数化(lambda set 特化)实现了闭包捕获不堆分配,这在非学术语言中尚属首次。去函数化类似于内联,能解锁大量后续优化,但实现极其困难。原始实现中团队遇到了严重 bug,直到 Ayaz Hafiz 在 OCaml 中原型化了一个新架构,才终于在新编译器中正确实现。Ayaz 的原型表明问题根源在于跨多个编译器阶段的架构,修复它需要重写大部分编译器。这是决定重写的关键原因之一,此外多位贡献者也独立表示他们计划重写各个部分。
关键要点
- 重写耗时 487 天,远长于 Bun 的 11 天,但原因在于架构变更而非语言本身;Bun 是直接移植,Roc 是重构。
- 功能持平里程碑:新编译器已能运行 Rocci Bird 游戏,生成二进制文件大小减少一半以上(从 60+KB 降至 31KB)。
- 热代码加载:Roc 编译器在开发时自动支持热加载,类似于解释型语言,但在高性能编译语言中罕见。
- 交叉编译:可生成针对 Alpine Linux 的静态二进制文件,且跨平台输出一致。
- 模式匹配字符串插值:新语言特性,编译时类型安全且零堆分配,用于 HTTP 路由等场景。
- 去函数化优化:通过 lambda set 特化实现的闭包捕获零堆分配,是 Roc 性能的关键,但实现复杂,导致架构重构。
- 重写理由:原始架构存在跨阶段 bug,修复需要重写大部分编译器;团队和贡献者一致认为重写是最好选择。
- 致谢与赞助:项目得到了多个组织和个人赞助,包括 rwx、Lambda Class、ohne-makler、martian、tweede golf、Vendr、NoRedInk 等。
意义与影响
这次重写标志着 Roc 语言编译器的重要转型。从 Rust 转向 Zig 不仅带来了热代码加载、交叉编译和模式匹配字符串插值等新功能,还通过架构重构解决了长期存在的性能 bug。去函数化优化的正确实现使得 Roc 在保持函数式语言优点的同时,实现了接近系统语言的运行时性能(零堆分配路由)。这证明了在编译语言中,精心设计的语言特性可以兼顾开发体验和运行效率。
对于 Rust 和 Zig 社区而言,Roc 的案例展示了两种语言在编译器开发中的不同取舍:Rust 的安全性和生态系统成熟,而 Zig 的简洁性和对底层控制的灵活性可能更适合需要大量定制化编译器的场景。虽然 Roc 团队强调这次重写的时间差异与语言无关,但它仍然为其他考虑重写的项目提供了参考:直接移植和架构重构的成本差异巨大,尤其是在语言特性需要深度编译器支持时。
Roc 目前仍处于 0.1.0 版本前的阶段,但新编译器的功能持平里程碑为后续正式发布奠定了基础。社区可以通过 roc-lang.org 首页的 WebAssembly 构建尝试新语法,这意味着 Roc 的易用性进一步提升。长期来看,Roc 对内存管理、模式匹配和热加载的独特设计,可能为函数式语言在 Web 开发、游戏和 CLI 工具等领域的实际应用开辟新路径。
