487 天!Roc 编译器从 Rust 重写为 Zig,功能对等且编译速度快百倍!
实现功能对等过去一年半Roc 编译器团队将 30 万行 Rust 代码重写为 Zig 代码新编译器功能与原编译器对等。达成这一里程碑后得以更新 2024 年开发的 WASM - 4 游戏 Rocci Bird其更新后的源代码更简洁使用 roc build --optsize 命令输出的 WASM 二进制文件仅 31KB是原编译器生成文件的一半。不过这并非正式版本发布计划在今年晚些时候推出 0.1.0 版本。同时多位贡献者为新编译器做出了重要贡献。重写耗时 487 天比 Bun 用 11 天将约 50 万行 Zig 代码重写为 Rust 代码长了 476 天原因在于 Bun 是直接移植而 Roc 要进行大量改动。热代码加载与跨平台编译二进制文件Roc 新编译器开发时会自动热代码加载如运行 roc server.roc 启动 Web 服务器修改代码后下次请求会自动使用新代码。还支持跨平台编译运行 roc build --targetx64musl 可构建能在 Alpine Linux 上运行的静态二进制文件且相同输入源代码在不同系统生成相同输出字节。支持字符串插值的模式匹配新编译器引入模式匹配中的字符串插值特性如处理 HTTP 请求的代码中/users/${id} 这种语法在编译时具有类型安全性且不会产生堆内存分配。可在新的 roc - lang.org 主页体验该语法。为何要彻底重写Roc 采用自动内存管理是首个在非学术领域实现通过 lambda 集合特化进行多态去函数化的语言闭包捕获不进行堆内存分配。但原实现遇到困难新编译器在 Ayaz Hafiz 原型开发基础上成功实现该功能。此外多位贡献者提出重写编译器不同部分因此决定彻底重写。为何选择 Zig团队基于使用 Rust 和 Zig 的经验决定用 Zig 构建整个编译器。主要考虑因素有编译时间上Rust 的 cargo 编译时间长预计 Zig 重写后会大幅缩短内存控制方面Rust 生态系统假设使用单一全局分配器而 Zig 支持细粒度分配器和数组结构体布局生态系统相关性上两个生态系统中与需求相关的包都少但 Zig 中可用的小众功能代码更多内存不安全代码辅助支持上Rust 隔离内存不安全代码而 Zig 有更多特性确保其正确性Roc 代码中使用 unsafe 较频繁所以 Zig 更有吸引力。没有借用检查器的日子内存安全问题中约 70% 是内存安全问题Rust 和 Zig 在处理部分问题方式相同16.4% 的使用已释放内存错误可被 Zig 的 ReleaseSafe 运行时检查、Rust 的借用检查器等捕获。ReleaseSafe 有运行时开销且可能触发 panicReleaseFast 在生产环境跳过检查。Rust 代码依赖项中的 unsafe 代码段存在内存不安全风险一些 Rust 项目曾出现内存不安全的 CVE 漏洞。Roc 考虑自身使用 unsafe 频率高且一些 Zig 项目有出色表现决定使用 Zig。重写后的内存安全情况从 Roc 问题跟踪器的 bug 报告分类统计看基于 Rust 的编译器有 21 个内存损坏的 bug基于 Zig 的有 10 个。Rust 编译器的内存损坏 bug 多因编译生成的机器指令导致Zig 编译器的 10 个中有 8 个是错误编译问题2 个是使用已释放内存的 bug。不同工具选择对项目影响不显著Roc 编译器与 Bun 面临不同挑战。编译时间Zig 重写后zig build --watch -fincremental 可在约 35 毫秒内重新编译约 45 万行 Zig 代码但 Zig 0.16.0 版本有 bug 使 -fincremental 失效需等待下一个稳定版本。对比不同版本的编译器编译时间Rust 编译速度有显著提升但 Zig 的 35 毫秒仍遥遥领先且代码行数更多。内存控制零解析反序列化Roc 新的磁盘缓存系统采用零解析反序列化技术编译器数据结构表示为带 32 位指针索引的数组可直接写入和反序列化反序列化速度等同于磁盘 I/O 速度。连续运行 roc check 或 roc test 可快速加载缓存结果。但这种方法有安全风险Rust 的借用检查器无法处理索引对应数组的问题compact_arena 库在特定场景也无法使用。