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 stash、git stash pop 和 git reset --hard,改变了其他任务依赖的全局状态。Jarred 随后禁止代理执行这类 Git 命令,也不允许它们在文件迁移阶段运行耗时的 Cargo 命令。任务最终分配到四个 Git worktree,每个 worktree 同时运行 16 个 Claude 代理。
峰值阶段,Claude 每分钟可以生成约 1300 行代码。官方复盘同时说明,这些代码刚生成时都不能工作,之后还要处理编译错误、链接问题、平台差异和行为偏差。
不同环节负责检查不同问题。迁移代理转换代码,审查代理主动寻找偏差,编译器检查类型和所有权,测试检查外部行为,CI 继续发现平台差异。Jarred 主要查看失败输出,判断审查代理能否发现错误;同类错误重复出现后,他会修改迁移规则,让后续任务采用新的约束。
传统代码审查要求 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,将计算推迟到确实缺少值的时候。迁移后的代码外形相近,求值顺序却不同。
负数时间戳也存在类似问题。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 反馈修复遗漏的问题。
参考文献
- Jarred Sumner. Rewriting Bun in Rust. Bun Blog, 2026
- Bun contributors. Rewrite Bun in Rust, PR #30412. GitHub, 2026
- Bun contributors. Rust port regressions, Issue #31477. GitHub, 2026
- Ciro Spaciari et al.. Remove the .zig porting-reference sources, PR #32621. GitHub, 2026
- Bun contributors. docs: say Bun is written in Rust, not Zig, PR #33065. GitHub, 2026
- Anthropic. Anthropic acquires Bun as Claude Code reaches $1B milestone. Anthropic News, 2025
- Alain Pham. Construction frame. Unsplash,封面摄影