2026 年 6 月 17 日发布的 Claude Code 2.1.181 已经使用 Rust 版本的 Bun。Linux 启动速度提高了约 10%,其余行为没有明显变化。对大多数用户来说,这次切换几乎不可见。

一个多月前,Rust 版本还是 GitHub 上一条很难完整渲染的巨型 PR。Rewrite Bun in Rust 在 5 月 14 日合入主分支,最终增加约一百万行代码,包含数千次提交。7 月 8 日,Bun 创始人 Jarred Sumner 发表了迁移复盘:他使用尚未公开的 Claude Fable 5,在 Claude Code 中同时运行约 50 个动态工作流,峰值调度约 64 个 Claude 实例,用 11 天让既有测试在多个目标平台上通过。

这批代码无法逐行人工审查。Bun 团队通过迁移规则、独立审查、编译器、测试和 Canary 反馈,检查新实现是否保留了旧实现的行为。

Bun 为什么改用 Rust

Jarred 在 2021 年 4 月开始编写 Bun。最初的实现从 esbuild 的 Go 代码逐行移植而来,随后加入了转译器、打包器、包管理器、测试运行器、HTTP 服务器和 Node.js 兼容层。

初版 Bun 大约用了一年。Zig 提供了直接操作系统接口、JavaScriptCore 和网络库所需的低层控制,简单语法和编译期能力也适合快速搭建这套工具链。Bun 如今每月已有超过 2200 万次 CLI 下载,Zig 支撑了项目早期的快速开发。

项目规模扩大后,资源管理变得越来越困难。JavaScript 值由垃圾回收器管理,文件描述符、socket、压缩流和原生缓冲区却需要手工释放。同一段代码可能同时涉及 JavaScript 异常、GC 扫描、引用计数、异步回调和 C ABI。程序员必须判断对象何时释放、回调是否还会访问它,以及异常路径是否遗漏了清理操作。

Bun v1.3.14 修复过一批这类缺陷:node:zlib 在异步写入过程中重置流会触发 use-after-free;UDPSocket.sendMany() 可能在用户回调改变连接状态后越界写入;tlsSocket.setSession() 每调用一次都会泄漏一个 SSL_SESSION;CSS 解析器在特定前缀和多层背景组合下可能 double-free。团队每次提交都会运行 AddressSanitizer,也长期进行 Fuzzilli 模糊测试和内存泄漏检测,但罕见的控制流仍会触发缺陷。

团队曾考虑继续使用 Zig,同时建立更严格的所有权约定、给共享对象增加智能指针,并要求代码审查逐条确认清理路径。这些约定仍需开发者手工遵守。遗漏一个 defer、多调用一次 deref(),或没有处理很少触发的错误分支,都可能造成内存问题。

Rust 将其中一部分规则放进了类型系统。值离开作用域时会运行 Drop,所有权和借用关系由编译器检查,无法证明安全的操作则集中在显式的 unsafe 边界内。

这次迁移没有重新设计 Bun。官方 PR 强调,架构和数据结构基本不变,没有使用 async Rust,并继续限制第三方依赖。新代码先保留 Zig 源码的结构,后续再逐步减少 unsafe 并调整为更符合 Rust 习惯的写法。截至本文写作时,大约 4% 的 Rust 代码位于 unsafe 代码块中,约 1.3 万个 unsafe 关键字分布在 2.7 万行代码里,多数用于访问 C++ 指针或调用 C 库。JavaScriptCore 和 C/C++ FFI 带来的风险仍然存在,不过这些位置现在更容易统计和单独审查。

按传统方式迁移 53 万多行 Zig,需要熟悉 Bun 的工程师投入很长时间。Claude 缩短了机械移植所需的时间,使团队能够在较短周期内尝试整体迁移。

一百万行迁移代码怎样验收

动手写代码之前,Jarred 先花了大约三小时和 Claude 讨论 Zig 模式如何映射到 Rust,并将结果整理成 PORTING.md。随后,工作流逐个分析结构体字段的生命周期:一个 Claude 实例提出方案,两个独立实例检查结论,最终结果写入 LIFETIMES.tsv,供后续代理查询。

正式试跑只选择了三个文件。每个文件由一个代理迁移,两个审查代理分别对照 Zig 原文、迁移指南和生命周期表检查偏差,再由另一个代理处理审查意见。确认流程有效后,任务才扩展到全部 1448 个 .zig 文件。

任务扩大后,多个 Claude 代理开始互相覆盖工作。它们在同一仓库里运行 git stashgit stash popgit reset --hard,改变了其他任务依赖的全局状态。Jarred 随后禁止代理执行这类 Git 命令,也不允许它们在文件迁移阶段运行耗时的 Cargo 命令。任务最终分配到四个 Git worktree,每个 worktree 同时运行 16 个 Claude 代理。

Bun 迁移工作流从 Zig 源码和规则开始,经过实现者、两个对抗式审查者、修复者,再进入编译器、测试和跨平台 CI,并将失败反馈回规则
实现、审查和修复分别运行在独立上下文中。某类错误重复出现时,Jarred 会修改任务规则,使后续批次采用新的约束。根据 Bun 官方迁移复盘整理

峰值阶段,Claude 每分钟可以生成约 1300 行代码。官方复盘同时说明,这些代码刚生成时都不能工作,之后还要处理编译错误、链接问题、平台差异和行为偏差。

不同环节负责检查不同问题。迁移代理转换代码,审查代理主动寻找偏差,编译器检查类型和所有权,测试检查外部行为,CI 继续发现平台差异。Jarred 主要查看失败输出,判断审查代理能否发现错误;同类错误重复出现后,他会修改迁移规则,让后续任务采用新的约束。

迁移数据页展示 11 天时间轴、峰值 64 个 Claude、约一百万新增行、6778 次提交、约 16.5 万美元 API 定价成本和 19 个已知回归
11 天是从开始迁移到所有平台测试通过并合入主分支的时间。约 16.5 万美元为官方按 API 定价计算的估值。Bun Blog, Rewriting Bun in Rust,2026-07-08

传统代码审查要求 diff 保持在人类可以阅读的范围内,而 GitHub 很难完整渲染这条包含一百万行新增代码的 PR。Bun 团队因此没有尝试逐行审查整个改动,而是检查迁移规则、抽查实现,并反复运行独立审查和自动化测试。迁移代理与审查代理使用不同上下文:前者负责完成转换,后者只负责寻找实现与原代码之间的差异。

官方复盘提到一个 Box<uv::Pipe> 的案例。代码调用异步 uv_close 后,Box 立即离开作用域,但 libuv 仍然持有已经释放的指针。这段代码可以通过编译,结构也与原实现相近。独立审查代理继续检查回调的生命周期,发现了 use-after-free 和随后发生的 double-free。

对外部行为的检查主要依靠 Bun 多年积累的测试。这些测试使用 TypeScript 编写,不依赖底层运行时采用 Zig 还是 Rust。各平台的测试套件包含约 5.7 万至 6.1 万项测试,以及超过一百万次 expect() 调用。团队反复运行 Debian x64、Linux ARM64、macOS ARM64、Windows x64 和 Windows ARM64 等目标的 CI,直到所有分片在同一次构建中通过。

迁移期间没有删除或跳过测试,Jarred 也亲自确认测试确实在执行。Claude 代理可能通过放宽断言或绕开失败路径让测试显示为通过,因此团队还要检查测试代码是否被削弱。

全部 CI 分片通过后,团队才合入 PR,但没有立即发布正式版本。Canary 版本用户、安全审计、基准测试和持续模糊测试继续检查新实现。团队后来记录了 19 个已知回归,其中一些问题只有在合入后的测试和实际使用中才出现。

测试通过后还发现了什么

不少回归来自 Zig 与 Rust 之间不明显的语义差异。迁移后的代码结构和变量名称都与原实现相近,也可以通过编译,但程序行为已经发生变化。

一个例子是 debug_assert!。原 Zig 代码把更新热重载图的 insertStale() 调用写在 assert() 参数中,Zig 的 assert 在发布构建里仍会执行参数表达式。迁移到 Rust 后,这段代码变成了 debug_assert!()。发布构建会删除整个表达式,带副作用的 insertStale() 也不再执行。调试构建运行正常,发布构建中的特定 React HMR 路径却会崩溃。

另一个例子是 unwrap_or。它会立即计算默认参数。即使 Option 已经包含值,默认表达式中的 unwrap() 仍会先执行,遇到 None 时直接触发 panic。这里需要使用 unwrap_or_else,将计算推迟到确实缺少值的时候。迁移后的代码外形相近,求值顺序却不同。

四行对照说明断言副作用、unwrap_or 立即求值、负数时间取整和奇数长度切片转换的 Zig 与 Rust 语义差异
四类问题都能通过编译。审查时需要沿着求值时机、取整、边界检查和析构顺序逐项比较。根据 Bun 官方公布的迁移缺陷与审查案例整理

负数时间戳也存在类似问题。trunc()-1.5 截断为 -1,剩余部分会产生负纳秒;floor() 得到 -2 和正 5 亿纳秒,符合 timespec 的表示要求。这个差异只会影响 1970 年以前的非整数时间,因此常规测试很难覆盖。

边界检查的差异也会改变兼容行为。Bun 原有的 Zig 辅助函数将字节重新解释为 u16 时,会忽略最后一个无法配对的字节;Rust 的 bytemuck::cast_slice 遇到长度不能整除时会直接触发 panic。原本能够容错读取的奇数长度 Blob,在包含 UTF-16 BOM 时可能导致进程崩溃。Rust 更严格的检查还暴露了一个原本隐藏在 Zig ReleaseFast 构建中的越界问题。这个检查有助于发现缺陷,但在兼容迁移中仍属于行为回归。

这些案例可以整理成语言迁移的专项检查表,包括求值时机、溢出行为、取整方向、析构顺序、断言副作用、切片转换、异常传播,以及发布构建是否删除了带副作用的表达式。

官方按公开 API 价格估算,合入前的工作消耗了 59 亿 uncached input tokens、6.9 亿 output tokens 和 720 亿 cached input token reads,总价约 16.5 万美元。官方将其与三名熟悉 Bun 的工程师工作一年作比较,认为模型调用的价格更低。这个估算只反映按公开价格折算的模型调用,不能直接代表整个迁移项目的实际成本。

Anthropic 已在 2025 年 12 月收购 Bun,团队使用的是预发布 Claude Fable 5 和 Claude Code 动态工作流。16.5 万美元并不等于 Anthropic 的实际推理成本,其他项目也很难获得相同的模型、预算和并发调度能力。

这次迁移还依赖 Bun 已有的测试基础。约六万项测试、多平台 CI、兼容性案例和基准测试覆盖了旧实现的大量外部行为,使团队能够判断迁移进度。缺少这些检查,即使能够快速生成迁移代码,也很难确认新旧实现是否一致。

Bun 选择一次性迁移,避免新旧实现长期并行。两套实现如果同时维护,持续进行的功能开发和缺陷修复会不断增加差异。高并发工作流和同一套语言无关的测试,让 Bun 将并行维护的时间缩短到 11 天。这种方法要求项目已有稳定的外部行为、具体的迁移规则和快速返回结果的测试系统,不适合直接用于测试不足的代码库。

Bun v1.4.0 将是第一个以 Rust 为主体实现的正式版本。迁移同时修复了多处旧有的内存泄漏和崩溃,并带来更小的二进制体积和约 2%–5% 的性能提升。团队记录的 19 个已知回归则说明,现有测试和审查仍有覆盖不到的地方。

在代码生成之后,Bun 团队先规定迁移方式,再让独立代理检查实现,并使用编译器、既有测试、多平台 CI、模糊测试和 Canary 版本逐步扩大检查范围。这些结果让团队能够在无法逐行读完 PR 的情况下决定是否合入。后续发现的 19 个回归说明,这些检查仍不完整,还需要实际使用继续补充。

Jarred 在复盘中用 Boring is good. 概括最终结果。Rust 版本投入使用后,多数 Claude Code 用户没有察觉变化,团队则继续根据 Canary 反馈修复遗漏的问题。

参考文献

  1. Jarred Sumner. Rewriting Bun in Rust. Bun Blog, 2026
  2. Bun contributors. Rewrite Bun in Rust, PR #30412. GitHub, 2026
  3. Bun contributors. Rust port regressions, Issue #31477. GitHub, 2026
  4. Ciro Spaciari et al.. Remove the .zig porting-reference sources, PR #32621. GitHub, 2026
  5. Bun contributors. docs: say Bun is written in Rust, not Zig, PR #33065. GitHub, 2026
  6. Anthropic. Anthropic acquires Bun as Claude Code reaches $1B milestone. Anthropic News, 2025
  7. Alain Pham. Construction frame. Unsplash,封面摄影