Bun 用 Rust 重写核心:11 天、6,778 次提交,以及真正改变的东西
Bun 团队在 2026 年 5 月干了一件不常见的事:他们用一份 Rust 移植版替换了运行时的整个原生核心,而且只用了 11 天。
公告最上面的声明定下了基调。Bun 在 2025 年 12 月被 Anthropic 收购。Bun 的创造者 Jarred Sumner 用了 Claude 的一个预发布版本——他们称为 Fable 5 的“Mythos 级模型”——完成了大部分工作。这不是什么思想实验。第一个 Rust 版本 Bun v1.4.0 现在已经在 canary 渠道了。
几个核心数字:
- 535,496 行 Zig(不含注释)变成了 Rust 代码库
- 5 月 3 日到 5 月 14 日之间,6,778 次提交
- 1,448 个
.zig文件被机械式移植成.rs - API 花费约 16.5 万美元(59B 未缓存输入 token,6.9 亿输出 token)
- 峰值:4 个 worktree 上同时跑着 64 个 Claude

Bun 一直是一场 Zig 的赌注
Bun 的起点是把 esbuild 的 JS/TS 转译器从 Go 逐行移植到 Zig。2021 年 4 月 16 日,Sumner 在 Hacker News 上看到只有一页的 Zig 语言参考,写下第一行 Zig。最初的版本——转译器、压缩器、打包器、兼容 npm 的包管理器、对标 Jest 的测试运行器、Node.js 的 API 表层——是一个人用一年时间写出来的,地点是奥克兰一间拥挤的公寓,那时候 LLM 还干不了这种活。
这场赌注赢了。Bun 的 CLI 现在每月下载量超过 2,200 万次。Claude Code 和 OpenCode 把它当作自己的运行时。Vercel、Railway、DigitalOcean 都提供官方支持。
但这么大的范围本身也是问题。
欠下的债:GC 内存挨着手动内存
JavaScript 是带垃圾回收的。Bun 内嵌了 JavaScriptCore(Safari 用的引擎),还有一堆 C/C++ 库:uWebSockets、BoringSSL、SQLite、lsquic。Zig 和 C 一样,不替你管理内存。Bun 的工作就是夹在带 GC 的语言和手动管理内存的原生代码之间,而这条边界正是它大多数严重 bug 滋生的地方。
公告里列了一串 v1.3.14 里修掉的 bug 作为例子:node:zlib 里的堆上 use-after-free、node:http2 里因重入回调导致的 use-after-free、UDPSocket.sendMany 里的越界写、每次 tls.connect 都会漏的内存、CSS 解析器里的 double-free。名单还在继续,而且都很技术。
Sumner 说得很明确:这不是 Zig 的错。“要不是 Zig,我们走不到今天,这一点我永远感激。“问题在结构上:在一个带 GC 的边界上处理生命周期,对任何不为这个场景设计的语言都很难;而 Zig 给出的答案——在每个调用点显式写 defer——在很少走到的错误分支里特别容易写错。
他考虑过 C++。Bun 里大约 20% 已经是 C++,再多用一些就能拿到构造函数和析构函数。但那样还是得靠代码评审来落实代码规范,内存损坏照样会发生。
Rust 的卖点不一样:在 safe Rust 里,use-after-free、double-free、“忘记释放”都是编译错误。借用检查器和 Drop 把一个代码规范层面的问题,变成了类型系统层面的问题。
策略:转译,不 redesign
重写名声不好,而且有道理。把 535K 行推倒重来,意味着功能和修复冻结一整年。Bun 选的形态是一次机械移植:架构不变、性能目标不变、功能不变、测试套件不变——只是换成 Rust。
两个决定主导了整个工作:
- 一次性做完,不要增量。Sumner 把 esbuild 移植到 Zig 的经验告诉他,增量重写会留下临时脚手架,拖累大于帮助。
- 让它看起来像转译出来的。目标是写出读起来像源 Zig 的 Rust,等 v1.4 发布之后再逐步重构成地道的 Rust。
关键一点:Bun 的测试套件是用 TypeScript 写的,所以它不在乎运行时是用什么语言实现的。这让他们可以把“所有测试变绿”当作做完的定义。
准备工作少但刻意。写代码之前,Sumner 花了大约 3 小时和 Claude 一起把 Zig 的模式映射到 Rust;那段对话成了 PORTING.md(后来上了 Hacker News)。第二遍梳理了整个代码库里每个结构体字段该有的 Rust 生命周期,序列化成 LIFETIMES.tsv。两份文档在动一行代码之前都先过了对抗式评审。
没动的东西:JavaScriptCore 和那些内嵌的 C/C++ 库。这次重写的是原生胶水代码和 Bun 自己写的代码,不是 JS 引擎。
执行:64 个 Claude,11 天
移植是以大约 50 个 Claude Code 里的“动态工作流”跑的,连续跑了 11 天。每个工作流都是一个循环:领一个任务,产出代码,拿去评审,落实反馈。
有意思的是评审模型。每个实现者旁边,都有两个或更多“对抗式评审者”——独立的 Claude 会话,被要求假定代码是错的,找出它失败的每一个理由。实现者从不评审,评审者从不实现。公告把这描述成对人类评审的模仿:作者想合入,所以得由另一方来查。
有几个机制值得拎出来:
- 先试跑。他们先移植了 3 个文件,才 all-in 那 1,448 个。
- worktree 分片。早期运行里,Claude 们互相踩脚,有人跑 git stash,有人跑 git reset。解决办法:4 个 worktree,每个 16 个 Claude,并且规定任务中途不准跑 git 或 cargo。
- 把编译错误当工作队列。cargo check 把大约 16,000 个错误倒进一个文件,按 crate 分组,64 个 Claude 一点点啃——4 个 worktree 上各 16 个“修/评/应用”循环。
- 循环依赖。Zig 实际上是一个编译单元;Rust 需要大约 100 个 crate。解开这些循环,才暴露出那 16,000 个错误里的大部分。
- 隔离。那些会耗光 TCP 端口、或者 spawn 大约 1 万个进程的压测,在 systemd-run 的 cgroup 下跑。机器还是几次把磁盘写满、崩了。
timeline
title Bun 的 Zig → Rust 重写(2026 年 5 月,11 天)
2026-05-03 : 移植分支开启(PR #30412)
2026-05-04 : 第一批 100 文件的草稿
2026-05-06 : 约 16,000 个编译错误,64 个 Claude
2026-05-08 : 第一次 CI 运行(972 个失败文件)
2026-05-09 : Linux x64 变绿
2026-05-11 : Windows 变绿(最后一个平台)
2026-05-14 : 六个平台全绿,合入
数字层面:峰值每分钟 1,300 行代码,最忙的一小时(5 月 6 日)695 次提交,最忙的一分钟 58 次。每一次提交落地前,都过了两个对抗式评审者的审。最终 diff 是 +1,009,272 行。零测试被跳过或删除。
CI 才是它变真的那一刻。第一次跑的两天后,失败的测试文件从 972 个掉到 23 个。再过一天半,Linux 全绿。Windows 最后在 5 月 11 日变绿;5 月 14 日的第 54202 号构建,六个平台全绿。
Rust 到底换来了什么
声明的目标是稳定性,而公告用硬数字撑住了这一点。
Bug。 Bun v1.4.0 修掉了 128 个在 v1.3.14 里能复现的 bug。
内存。 Drop 取代了每个调用点上的 defer。公告里的例子:在一个进程里把同一个 60 模块的项目打包 2,000 次。在 v1.3.14 里,每次构建永久漏大约 3 MB;在 v1.4.0 里,内存平了。
| 构建次数 | v1.3.14 | v1.4.0 |
|---|---|---|
| 500 | 1,914 MB | 526 MB |
| 1,000 | 3,506 MB | 586 MB |
| 1,500 | 5,097 MB | 608 MB |
| 2,000 | 6,745 MB | 609 MB |
Sumner 写道,之前在 Zig 里修这个问题的尝试没能合入,因为缺少和 Drop 对等的机制,让人很难有信心合。
二进制体积。 光是 Rust 重写就砍掉了 Windows 3.8 MB、macOS 5.5 MB、Linux 6.8 MB——主要因为去掉了过量的 Zig comptime。进一步的链接器优化(相同代码折叠、裁掉 ICU 数据)让 Linux 和 Windows 上总体积缩了约 20%。
| 版本 | 平台 | 体积 |
|---|---|---|
| v1.4.0 | Windows | 76 MB |
| v1.3.14 | Windows | 94 MB |
| v1.4.0 | Linux | 70 MB |
| v1.3.14 | Linux | 88 MB |
速度。 C/C++ 和 Rust 之间的跨语言 LTO,让编译器能跨语言内联。Bun 测出了 2–5% 的提升:
Bun.serve:169.6k → 177.7k req/s(+4.8%)express:64.5k → 66.6k(+3.2%)next build:13.62s → 13.03s(+4.5%)tsc -b --force:0.94s → 0.89s(+4.7%)
Prisma 把它的 Compute 公测搬到了 Rust 重写版上。Alexey Orlenko 说:“我们遇到过内存泄漏,还有一个在 VM 暂停又恢复之后恢复不了的数据库连接池。Rust 重写版出来后,我们用同样的失败模式测了一遍,它完美扛住了。”
Claude Code v2.1.181(6 月 17 日)及之后都用上了 Rust 版。在 Linux 上启动快了 10%。Sumner 对用户能感知到的变化的评价是:“Boring is good.”(无聊是好事。)
移植翻车的地方
把 535K 行忠实地转译一遍,不可能零回归。公告记录了 19 个已知回归,全都修了。有代表性的几个,是两种语言之间极小的语义缝隙:
debug_assert!里的副作用。 Zig 的 assert 是个函数,所以参数在每个构建里都会跑。Rust 的 debug_assert! 是个宏,在 release 里整段被抹掉——一个会改动 HMR 状态的 insert_stale 调用,悄无声息地不跑了。(#30678)- 奇数长度的切片。 Zig 的辅助函数会忽略末尾多出的一个奇数字节;bytemuck::cast_slice 碰到它会 panic。一个 UTF-16 BOM 后面跟奇数个字节的 Blob.text() 开始崩。(#31188)
- 边界检查。 Zig 的 ReleaseFast 会去掉边界检查;Rust 的 release 保留。一个占位的常量把文件名 intern 的天花板从 840 万掉到 27 万,真实项目撞上了。(#31503)
comptime格式字符串。 Zig 在编译期求格式字符串,所以颜色标记在参数代入之前就没了。Rust 没有 comptime,于是标记解析器把 OSC 8 超链接里的一个反斜杠也吃掉了。(#30693)
这类 bug,只有两种语言长得一样、其实不一样的时候才会冒出来。
这意味着什么
去掉 LLM 这层不谈,这里有一个实在的工程故事:一个项目撞上了手动内存管理在它的规模上能给出的天花板,于是选了 Rust 的借用检查器作为工具,让一整类 bug 从“被劝阻”变成“不可能”。
LLM 这层,是会让人吵起来的部分。Sumner 说得很坦率:这事本来要三个对代码库有完整上下文的工程师干大约一年,而且他们根本不会去做——现实的选择是“什么都不做,继续修上面那些 bug”。结果是一个工程师盯着 64 个 Claude,11 天、约 16.5 万美元干完了。
Bun 的 Rust 代码里,大约 4% 在 unsafe 块里(约 78 万行总量里,约 2.7 万行、约 1.3 万个 unsafe 关键字),而且这些块里 78% 只有一行。合并之后,他们跑了 11 轮来自 Claude Code Security 的安全评审,还给每个解析器加了 24/7 覆盖引导的模糊测试——至今跑了一千亿次,大约 15 个 PR。
我会盯着的是可维护性问题。Sumner 说这份 Rust 读起来像那份 Zig,还拿 canMergeSymbols 做了个并排对比来证明。赌的是:一份忠实的移植可以按文件逐个评审,而这正是一个人敢在一百万行的 diff 上签字的原因。
他的收尾这句话,会很有意思地老去:“One engineer can do a lot more today than a year ago.”(今天一个工程师能做的,比一年前多太多了。)