Bun 1.4: 使用 AI 用 Rust 重写运行时

Bun 1.4: 使用 AI 用 Rust 重写运行时

Bun 在 v1.4.0 版本中将其核心运行时用 Rust 重写,旨在系统性地消除内存安全漏洞并提高稳定性。通过利用 Claude Fable 5 的预发布版本和自动化的“对抗性审查”工作流,团队在 11 天内将超过 500,000 行 Zig 代码移植到了 Rust,实现了二进制文件体积减少 20%,性能提升 2%-5%。

为什么 Bun 从 Zig 转向 Rust

Bun 最初是使用 Zig 构建的,以利用其底层控制能力和高性能。然而,该项目的庞大规模——包括转译器、包管理器、测试运行器和 Node.js API 实现——带来了显著的稳定性挑战。

系统性内存安全问题

重写的主要驱动力是一类难以通过代码风格指南或手动审查来解决的循环性内存错误。在 Bun v1.3.14 中,团队发现了许多关键问题,包括:

  • Use-after-free 崩溃,由异步操作或重入式 JS 回调在 node:zlibnode:http2 中触发。
  • Double-free 崩溃,发生在 CSS 解析器中。
  • Memory leaks(内存泄漏),由于引用计数下溢或缺失清理调用,在 crypto.scrypttlsSocket.setSession()fs.watch() 中出现。

虽然团队使用了 Address Sanitizer (ASAN) 和通过 Fuzzilli 进行的 24/7 模糊测试,但 Zig 缺乏自动内存管理,这意味着每一次分配都需要细致的手动审查。选择 Rust 是因为其借用检查器(borrow checker)和 Drop trait 可以将这些内存安全错误转化为编译器错误,从而提供比手动代码审查更快、更可靠的反馈循环。

AI 驱动的重写过程

Bun 团队并没有采用传统的增量重写(这通常会引入临时的“胶水”代码),而是对整个代码库进行了机械式移植。这是通过使用 Claude Code 和一系列动态工作流实现的。

“对抗性审查”工作流

为了确保 LLM 生成代码的质量,团队实施了分上下文审查系统:

  1. 实施者 (The Implementer): 一个 Claude 实例,根据原始 Zig 源码和移植指南编写 Rust 代码。
  2. 对抗性审查者 (The Adversarial Reviewers): 两个独立的 Claude 实例,它们只接收 diff。其唯一目标是发现错误并证明代码是错误的。
  3. 修复者 (The Fixer): 最后一个实例,负责应用审查者的反馈。

这一过程捕捉到了原本可以通过标准编译器检查的严重错误,例如由 libuv 中 uv_close 的异步特性导致的 js_bun_spawn_bindings.rs 中的 use-after-free/double-free 错误。

执行与规模

  • 时间线: 11 天(5 月 3 日至 5 月 14 日)。
  • 工作量: 6,502 次提交,峰值输出为每分钟 1,300 行代码。
  • 基础设施: 64 个并发 Claude 实例运行在 4 个工作树上。
  • 成本: API token 消耗约 165,000 美元。

Bun v1.4.0 中的技术改进

内存使用与稳定性

向 Rust 的 Drop trait 的过渡消除了几种可检测的内存泄漏。例如,在 Bun.build() 中,v1.3.14 的内存使用量随构建次数线性增长(在 2,000 次构建后达到 6.7 GB),而在 v1.4.0 中,内存水平稳定在约 609 MB。

二进制文件体积与性能

  • 二进制文件体积: 在 Linux 和 Windows 上减少了约 20%(例如,Windows 二进制文件从 94 MB 缩小至 76 MB)。这归功于减少了 comptime 的使用以及实现了诸如 Identical Code Folding 等链接器优化。
  • 吞吐量: 在各种框架(Bun.serve, Elysia, Fastify)中,HTTP 吞吐量提升了 2.8% 至 4.8%。
  • CLI 速度: 应用工作负载如 next buildtsc -b 的性能提升了 2.2% 至 4.7%。
  • 栈空间: 由于 LLVM 的 llvm.lifetime.startllvm.lifetime.end 内置函数,递归下降解析器(JSON, TOML 等)现在使用更少的栈空间。

移植挑战与回归问题

尽管采用了自动化过程,重写仍引入了 19 个已知的回归问题,主要源于 Zig 和 Rust 的语言语义差异:

  • 宏擦除 (Macro Erasure): 一个错误发生于 Rust 中的 debug_assert! 在发布版本中擦除了副作用调用 (insert_stale),而 Zig 的 assert 是一个始终运行的函数。这破坏了某些 React 项目的 HMR。
  • 切片处理 (Slice Handling): Rust 的 bytemuck::cast_slice 在处理奇数长度的切片时会发生 panic,而原始 Zig 代码则忽略了末尾字节。这导致 Blob.text() 在特定的 UTF-16 情况下会发生 panic。
  • 边界检查 (Bounds Checking): Rust 的发布版本会保留边界检查,这揭露露出了一个之前被 Zig 的 ReleaseFast 模式隐藏的模块解析器中的 off-by-one 错误。

社区与行业视角

来自 Hacker News 讨论的见解突显了软件工程范式的转变:

"设计并实现全面的测试套件是真正的核心工作,一旦你拥有了它,就让 LLM 去大显身手吧。"

批评者指出,与人工团队相比,高昂的 token 成本(16.5 万美元)可能会抵消部分人工成本的节省,而其他人则指出,重写的成功在很大程度上取决于 Bun 现有的、与语言无关的 TypeScript 测试套件,这为 AI 迭代提供了必要的“可验证的奖励”。

Sources