Bun 的 Rust 重生:Anthropic 如何用 11 天、16.5 万美元把 100 万行 Zig 改写成 Rust——跨语言 AI 迁移工程全链路拆解
一个 JavaScript 运行时的发展史,突然变成了一堂「AI 能不能重写整个代码库」的公开课。本文不聊口水战,只拆工程:为什么 Bun 要从 Zig 搬家到 Rust、搬家过程到底用了什么手段、迁移后架构与性能发生了什么变化,以及这件事对普通工程师意味着什么。
一、背景介绍:Bun 到底是个什么物种
如果你这两年没太关注前端工具链,先补一句背景:Bun 是一个全家桶式 JavaScript/TypeScript 运行时,由 Jarred Sumner 在 2022 年推出,目标是「一个二进制文件干掉 Node.js + npm + webpack + jest 的整个工具链」。它把运行时、包管理器、打包器、测试运行器、SQLite 客户端、HTTP 客户端全部塞进一个用系统语言写的可执行文件里。
Bun 身上有两个标签一直很醒目:
- 快。官方反复强调启动时间约 3ms,对比 Python 慢约 15 倍;
bun install比npm install快 5–10 倍;早期基准里 HTTP 吞吐能跑到每秒上百万请求。 - 用 Zig 写的。这是它和 Deno(Rust)、Node(C++)最大的身份差异。Zig 是一门没有垃圾回收、强调显式内存控制、定位「现代 C 替代品」的系统语言,Bun 是 Zig 在生产环境里最出圈的作品之一。
然后剧情在 2025 年底急转。2025 年 12 月,Anthropic 宣布收购 Bun,对外口径是:Bun 会成为 Claude Code(Anthropic 的 AI 编程 Agent)背后的运行时、包管理器、打包器和测试工具。从那一刻起,Bun 不再只是一个「更快的 Node」,而是 Anthropic AI 编程基础设施的一块底座。
真正引爆技术圈的是 2026 年 5 月的一件事:Bun 的核心实现从 Zig 被整体迁移到了 Rust。PR #30412,标题直白得有点挑衅——「Rewrite Bun in Rust」,于 2026-05-14 合并,规模是 6755 个 commit、2188 个文件变更、新增代码超过 100 万行。创始人 Jarred Sumner 后来在社交媒体上披露:他利用一批并行运行的 Claude 智能体,仅用 11 天就把 Bun 从 Zig 移植到了 Rust,按 API 定价核算成本约 16.5 万美元。
到了 7 月,Anthropic 旗下 Bun 又宣布用 Claude Code 把核心再做了一轮 Rust 化重构;8 月 11 日,Zig 语言创始人 Andrew Kelley 公开开火,称这批靠 Claude 生成的 Rust 重构版是「没人把关的烂代码(nobody-reviewing shitty code)」。
于是问题来了:把一个百万行级别、对性能极度敏感、吃透系统底层的运行时,用 AI 在两周内跨语言重写,这到底是工程奇迹,还是技术债炸弹?要回答这个问题,我们得先把「跨语言迁移」这件事的技术本质讲清楚。
二、核心概念:跨语言迁移不是翻译,是重焊电路板
很多人直觉里以为「Zig 转 Rust」像把英文文档机翻成中文——逐行替换语法就行。这是最大的误解。两种系统语言之间的迁移,本质上是在同一份语义契约下,重焊电路板:对外暴露的 API、字节布局、ABI 约定必须保持不变,但底下每一根「线」的走法都变了。
2.1 机械式移植 vs 语义重写
跨语言迁移工程里存在一条光谱:
- 机械式移植(mechanical port):逐函数、逐结构体地把 Zig 代码 1:1 翻成 Rust。用得最多的是「保持数据结构布局和函数签名不变,只改内存管理范式(手动
malloc/free→ RAII/Ownership)」。好处是行为可预测、回归风险低;坏处是迁移完的代码往往「Rust 其外、Zig 其内」,没法享受新语言真正的好处。 - 语义重写(semantic rewrite):借迁移机会重构架构,比如把 Zig 里大量用
defer和手动生命周期管理的模块,改成 Rust 的Arc<T>/Rc<T>/ 借用检查强制约束的写法。好处是拿到语言红利;坏处是「你其实在同时做两件事」——既换语言又改逻辑,调试地狱。
Bun 的情况特殊:它是在 Claude 智能体集群的辅助下完成的。公开资料显示仓库里出现过一个名为 claude/phase-a-port 的分支,里面是数十万行 AI 生成的 Rust 代码与原始 Zig 实现并排摆放,还附带一份长达 576 行的「Zig 到 Rust 迁移指南」。这份指南的存在本身就很说明问题:Bun 团队选择的是「机械式移植优先」路径——先让 AI 把代码按 1:1 投影过去,用测试套件(100% 通过)作为行为守门员,再在稳定后做局部语义优化。这条路最务实:先用可验证的等价性兜住风险,再谈改进。
2.2 一个具体例子:HTTP 解析层的迁移心智
为了让你感受差异,下面给一段「概念对照」。这不代表 Bun 的真实源码(百万行代码不可能逐字公开),但精确还原了两类语言在表达同一个底层操作时的范式区别。
Zig 风格(手动生命周期,调用方负责释放):
fn parseHeaders(alloc: std.mem.Allocator, buf: []const u8) !*HeaderMap {
const map = try alloc.create(HeaderMap);
map.* = HeaderMap.init(alloc);
// ... 解析 buf,写入 map ...
return map; // 调用方必须记得在某处 alloc.destroy(map)
}
Rust 风格(所有权转移,编译器强制释放路径):
fn parse_headers(buf: &[u8]) -> Result<HeaderMap, ParseError> {
let mut map = HeaderMap::new();
// ... 解析 buf,写入 map ...
Ok(map) // map 的所有权随返回值移交,离开作用域自动 drop
}
表面看只是「alloc 参数没了」,但底下是范式地震:Zig 版本把「谁来释放」的责任推给人和代码审查;Rust 版本把它焊死在类型系统里。对一个有上千处内存分配路径的运行时来说,这种范式迁移的工程量,远大于语法转换本身——而 AI 在这里的价值,恰恰是把这种「注意力密集但模式可归纳」的活儿批量干掉。
2.3 为什么是 Rust,而不是继续 Zig
公开报道点出了几个核心动因,我把它归纳成工程师能共情的三条:
- Zig 的反 AI 贡献政策与 Anthropic 撞车。Zig 社区有严格的「不接受 AI 生成代码贡献」政策。Bun 团队用 Claude 做的编译器优化没法合进 Zig 上游,等于被锁死在自维护的 Zig fork 里。一旦母公司是 Anthropic,这个矛盾从「不舒服」升级成「战略不可持续」。
- Zig 语言本身还在快速迭代、频繁破坏性变更。底层依赖一个剧烈变动的语言,对需要长期稳定的运行时是慢性毒药。Rust 的稳定性和 ecosystem 成熟度更适合做「十年底座」。
- 人才与生态密度。Rust 的 contributors、库(crates)、工具链(clippy、miri、rust-analyzer)体量远超 Zig。对一个要嵌入 Claude Code 这种超大体量系统的项目,招聘和协作成本直接决定生死。
说白了:Bun 换语言不是因为 Zig 不好,而是因为「Zig + AI 母公司 + 长期基础设施」这个组合在现实里跑不通。
三、架构分析:Bun 的「全家桶」是怎么搭起来的
要评价迁移好坏,得先看懂 Bun 长什么样。Bun 的架构可以用一句话概括:一个用系统语言写的薄内核,外面裹着完整的 JS 工具链,内核里嵌了 JavaScriptCore(WebKit 的 JS 引擎)。
3.1 内核:为什么是 JavaScriptCore,不是 V8
Node.js 用 V8(Chrome 的引擎),Deno 也用 V8。Bun 偏偏选了 JavaScriptCore(JSC,Safari 的引擎)。这是个有战略含义的选择:
- JSC 的启动成本极低,契合 Bun「3ms 启动」的卖点。V8 为了峰值吞吐会做更重的预热,冷启动偏慢。
- JSC 的字节码缓存(JDSC / bytecode caching)机制让 Bun 能把转译结果直接落盘复用,配合
bun run热路径几乎零重复编译。
代价是:要和 JSC 的 C++ API 打交道,得在系统语言层写大量胶水。这层胶水,原来在 Zig 里,现在在 Rust 里。迁移后这层胶水的质量,直接决定 Bun 的稳定性和性能。
3.2 工具链:五个命令吃遍天
Bun 的理念是「一个工具搞定全栈」,对照 Node 生态的碎片化:
| 能力 | Node 生态 | Bun |
|---|---|---|
| 运行时 | node | bun run |
| 包管理 | npm/yarn/pnpm | bun install(内置) |
| 转译 | tsc / esbuild / vite | bun(原生支持 TS/TSX) |
| 打包 | webpack / esbuild / vite | bun build(内置) |
| 测试 | jest / vitest / mocha | bun test(内置) |
这种「全家桶」设计的工程意义在于:所有工具共享同一个解析器、同一个模块图、同一个缓存。当你 bun run 一个 TS 文件时,转译器和运行时是同一进程内的同一份代码,没有进程边界的序列化开销。这比「tsc 先编译出 JS,再丢给 node 跑」少了一整段流水线。迁移到 Rust 后,这套共享内核的模块化边界是在 Rust 的 crate 体系下重新划定的。
3.3 迁移工程的真实形态:并行 Agent + 测试守门
从公开信息反推,Bun 的迁移工程大致是这样一个结构:
┌─────────────────────────────┐
│ Zig 代码库 (权威源) │
└──────────────┬──────────────┘
│ 切片成模块级任务
▼
┌──────────────────────────────────────────────┐
│ 并行 Claude Agent 集群(每个负责一类模块) │
│ - 解析器/AST - HTTP层 - SQLite绑定 │
│ - 包管理 - 测试器 - 文件系统 │
└──────────────────────┬───────────────────────┘
│ 生成 Rust 投影 (phase-a-port)
▼
┌──────────────────────────────────────────────┐
│ 576 行迁移指南 + 测试套件 (100% 通过为门禁) │
│ - 行为等价性校验 - ABI/字节布局对齐 │
└──────────────────────┬───────────────────────┘
│ 合入 PR #30412
▼
┌─────────────────────┐
│ Rust 版 Bun 主干 │
│ 后续局部语义优化 │
└─────────────────────┘
这套打法的聪明之处在于:用测试套件当「真理裁判」,把 AI 的自由度锁死在「行为等价」这个硬约束里。AI 可以随便翻代码,但只要 bun test 全绿、ABI 对齐,你翻出来的 Rust 行为就和旧 Zig 一致。这比「让 AI 自由重构 + 人工 review 每一行」在两周内完成百万行,现实得多。
但这也是 Zig 创始人批评的命门:100% 测试通过 ≠ 没有烂代码。测试覆盖不到的边界、性能回归、可读性崩塌、长期可维护性,这些恰恰是 AI 机械迁移最弱的地方。AI 能证明「现在能跑」,很难保证「三年后别人还改得动」。
四、代码实战:用 Bun 写点真东西
讲了半天架构,落回实用主义。Bun 作为开发者,最该关心的永远是「它写起来爽不爽、跑起来快不快」。下面用一组可运行示例,展示 Bun 的 API 设计哲学——尽量让常见任务一行搞定,少引依赖。
4.1 一个完整的 HTTP 服务,零依赖
// server.ts
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/health") {
return Response.json({ ok: true, ts: Date.now() });
}
if (req.method === "POST" && url.pathname === "/echo") {
const body = await req.json();
return Response.json({ youSent: body });
}
return new Response("Hello from Bun!", {
headers: { "content-type": "text/plain; charset=utf-8" },
});
},
});
console.log("listening on http://localhost:3000");
跑它只需要 bun run server.ts。注意:这里没有 express、没有 koa、没有 @types/node,原生 fetch / Response / Request 就是 Web 标准 API。Bun 直接实现了 Web 标准,而不是 Node 那套 http.createServer 回调风格。这是 Bun 和 Node 在「心智模型」上的根本差异:Bun 尽量让你写浏览器里也能跑的代码。
4.2 内置 SQLite,别再引 node-sqlite3 了
Node 生态里接 SQLite 通常要装 better-sqlite3(原生编译,经常和 Node 版本打架)或 node-sqlite3。Bun 直接内置:
// db.ts
import { Database } from "bun:sqlite";
const db = new Database("app.db", { create: true });
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");
const insert = db.query("INSERT INTO users (name, age) VALUES (?, ?)");
insert.run("Alice", 30);
insert.run("Bob", 25);
const all = db.query("SELECT * FROM users WHERE age > ?").all(20);
console.log(all); // [{id:1,name:'Alice',age:30},{id:2,name:'Bob',age:25}]
// 用参数化查询防注入,Bun 的 query 直接返回可复用语句对象
const byName = db.query("SELECT * FROM users WHERE name = ?");
console.log(byName.get("Alice"));
bun:sqlite 是同步 API(基于 SQLite 本身的同步本质),比 async 包装更符合直觉,也更快。迁移到 Rust 后,这层绑定是在 Rust 里重新封装 JSC 对象的,性能关键点在于「JS 值和 Rust 结构体之间零拷贝转换」做得好不好。
4.3 文件 I/O:Bun.file 比 fs 更符合直觉
// io.ts
const pkg = Bun.file("package.json");
const text = await pkg.text();
const json = JSON.parse(text);
console.log("name =", json.name);
// 写文件也一行
await Bun.write("dist/readme.txt", "# 构建产物\n生成于 " + new Date().toISOString());
// 流式拷贝大文件,零额外依赖
const src = Bun.file("big.iso");
const out = await Bun.file("copy.iso").writer();
await out.write(await src.arrayBuffer());
await out.end();
Bun.file 返回的是 Blob 的子类,天然支持 text() / json() / arrayBuffer() / stream(),和 Web 标准对齐。
4.4 测试:bun test 自带、零配置
// math.test.ts
import { expect, test } from "bun:test";
function add(a: number, b: number) {
return a + b;
}
test("add 正数", () => {
expect(add(1, 2)).toBe(3);
});
test("add 负数", () => {
expect(add(-1, -2)).toBe(-3);
});
bun test 直接跑,不用装 jest、不用配 babel。还能用 bun test --watch 监听,用 bun test --coverage 出覆盖率。
4.5 构建:bun build 一行打生产包
// build.ts
await Bun.build({
entrypoints: ["./src/index.tsx"],
outdir: "./dist",
target: "browser",
minify: true,
// 支持自动切 code-splitting
naming: "[dir]/[name].[hash].js",
});
console.log("build done");
Bun.build 底层是 Bun 自己的打包器(早期用内部实现,迁移 Rust 后这层也在重构)。对中小项目,这套内置方案已经够用,省去了 webpack/vite 的配置地狱。
4.6 新内核带来的新能力:Bun.Image 与 HTTP/3
迁移到 Rust 内核后的 Bun 1.3.14,官方点名了两个新能力:内置图像处理 Bun.Image 和 HTTP/3 支持。
// image.ts(基于 Bun 1.3.14+ 的 Bun.Image)
const image = await Bun.file("input.png").image();
const resized = image.resize(200, 200);
await Bun.write("thumb.png", resized);
// 还能转换格式、取尺寸
console.log(image.width, image.height, resized.type);
// http3.ts(HTTP/3 服务端,启用需对应内核支持)
Bun.serve({
port: 443,
// 在支持 HTTP/3 的构建里,Bun 会自动协商 QUIC
tls: { cert: "cert.pem", key: "key.pem" },
fetch(req) {
return new Response("served over HTTP/3 if client supports it");
},
});
Bun.Image 的意义不只是「少装一个 sharp 依赖」——它是把高频、CPU 密集、原本要 spawn 子进程或引原生模块的能力,焊进运行时内核。这正是「全家桶」哲学的延伸:越常用的东西,越不该让开发者自己去拼依赖。
五、性能优化:迁移后到底变快了还是变慢了
这是最该关心的工程问题。先把预期讲清楚:跨语言迁移对运行时性能的影响,主要来自四类开销的再平衡——启动、内存、CPU 密集路径、并发。
5.1 启动速度:Rust 不输 Zig
Bun 的招牌是 3ms 启动。Zig 和 Rust 在二进制启动开销上都属于「极轻量」阵营(都没有 VM 预热、没有 GC 停顿起步)。机械式移植后,启动路径基本是 1:1 对应,实测显示启动时间持平甚至略快。原因是 Rust 社区的链接优化(rustc + lld + thin LTO)已经非常成熟,Bun 团队能直接复用这套优化链。
5.2 内存与 GC:JSC 才是主角
要澄清一个常见误判:Bun 的内存行为,主要由嵌入的 JavaScriptCore 决定,而不是由「写内核的语言」决定。Zig 还是 Rust 写的胶水层,影响的是「胶水层自身的内存安全」,不是 JS 对象的内存管理。所以迁移对 JS 层 GC 行为几乎没有影响——你看到的还是 JSC 的 GC。
真正变化的是胶水层的内存安全等级:Zig 版本靠人力和 code review 保证不内存泄漏;Rust 版本靠编译器在编译期堵住一大类 use-after-free / double-free / 数据竞争。这意味着长期运行的服务(比如用 Bun 跑长生命周期 API 服务),内存泄漏的「人为引入面」被显著收窄。
5.3 CPU 密集路径:取决于迁移质量
HTTP 解析、模块转译、SQLite 绑定这些 CPU 密集路径,性能取决于 AI 生成的 Rust 代码质量。机械式移植如果简单粗暴地 unsafe 满天飞、或者用 Rc<RefCell<T>> 到处套来绕过借用检查,短期能跑但可能比原 Zig 慢。这恰恰是 Zig 创始人批评的落点:「能跑」和「跑得好」之间,隔着一个有经验的系统程序员。
合理的判断是:Bun 团队后续(7 月那轮 Claude Code 重构)必然在做「把机械移植产物逐步打磨成地道 Rust」的工作。这一步决定迁移是「技术升级」还是「技术置换」。
5.4 一个可复现的微基准:启动对比
你可以在自己机器上跑这个对比(需要同时装了 node 和 bun):
# 测 Node 启动一个空脚本
time node -e "console.log('hi')"
# 测 Bun 启动同样的事
time bun -e "console.log('hi')"
在多数机器上你会看到 Bun 的 real 时间明显更短,差距来自 Bun 省去了 Node 的 V8 预热和模块解析链路。注意这个对比和 Zig/Rust 无关——无论内核什么语言,Bun 的快都主要源于 JSC + 极简启动路径。
5.5 给生产环境的一张调参清单
如果你准备把 Bun 用到生产(尤其是迁移后的版本),建议按这个优先级排查:
- 确认版本 ≥ Bun 1.3.14,并核对该版本是否默认启用 Rust 内核(部分能力可能还在 canary)。
- 跑一遍完整
bun test+ 你自己的压测。AI 迁移版本最该验证的是「边界行为」,别只看 happy path。 - 监控长生命周期服务的内存曲线至少 24 小时,确认没有渐进式泄漏。
- HTTP/3 和 Bun.Image 先在小流量灰度,新能力 + 新内核双重变量,别一起上生产。
- 保留 Zig 版本回滚预案,直到你对 Rust 版的稳定性有信心。
六、总结展望:AI 重写软件的边界在哪里
把 Bun 这件事放回更大的图景里看,它其实是 2026 年一个标志性事件:人类第一次用 AI 在两周内,把一个百万行、对性能极度敏感的底层基础设施,跨语言搬了家,而且「跑起来了」。 这本身已经改写了我们对「软件可维护性」的假设。
但作为工程师,我更想给你三个冷静的判断:
第一,AI 迁移擅长「可验证的等价」,不擅长「不可验证的优雅」。 测试套件能守住行为正确性,却守不住代码可读性、性能天花板和长期可维护性。Bun 的迁移成功,前提是它有极其完备的测试守门;如果你的项目测试覆盖率不到 60%,照抄这套打法就是自杀。
第二,语言迁移的本质是「政治 + 工程」的双重决策,技术优劣只是其中一块。 Bun 换 Rust,核心推手是「Zig 反 AI 政策撞上 Anthropic 母公司」这个现实矛盾,而不是「Rust 比 Zig 快」。选型时别只看 benchmark,要看「这门语言的治理、生态、和你的组织是否同频」。
第三,对普通开发者,Bun 的价值不取决于它内核是 Zig 还是 Rust。 你用 Bun 是因为 bun install 快、bun run 快、内置 SQLite 和测试省心,而不是因为它用哪门语言写的。内核重写只要不破坏这套开发者体验,对你就只是「无感升级」。
最后说句实在话:Zig 创始人那句「没人把关的烂代码」,戳中的是 AI 时代所有「又快又猛」工程的软肋。Bun 用 11 天和 16.5 万美元证明了「AI 能重写一切」的可能性,但「能重写」到「重写得好、改得动、扛得住」,中间还隔着一整个工程师职业存在的意义。这件事最精彩的不是结局,而是它把那个老问题又甩回了我们脸上——当机器能写代码,人的价值到底在哪? Bun 的答案是:在提出正确的问题、设定守门的标准、以及在机器跑完之后,还愿意一行行把代码读到懂的人手里。
参考资料(公开报道汇总):Bun 官方仓库 PR #30412「Rewrite Bun in Rust」、Anthropic 收购 Bun 公告、Jarred Sumner 关于 11 天并行 Claude Agent 迁移的公开披露、Zig 创始人 Andrew Kelley 2026-08-11 公开评论、Bun 1.3.14 发布说明(Bun.Image / HTTP/3 / Rust 内核)。文中 Zig↔Rust 代码为说明性对照示例,非 Bun 真实源码。