Bun 2.0 深度解析:从 Zig 到 Rust,Claude Code 如何用 9 天重写百万行代码
引言:一次震动了整个开发者社区的代码合并
2026年5月14日,GitHub 上出现了一条引发 700+ 热度的讨论帖:PR #30412 ——「Rewrite Bun in Rust」被合并进 Bun 主分支。这条 PR 包含 6755 个提交、涉及 2188 个文件,新增超过 100 万行 Rust 代码,而生成它们的不是人类程序员,是 Claude Code。
这是迄今为止规模最大的 AI 参与真实开源基础设施项目的案例。
故事的前情是:2025年12月,Anthropic 收购了 Bun。Bun 的创始人 Jarred Sumner 在 X 上发了一条轻描淡写的推文:「Bun v1.3.14 明天发布。如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本。」六天后,这个预言成真了。
很多人第一反应是「又一个 JS 运行时投入 Rust 怀抱了」。确实,Deno 最早用 Go 写,后来换成 Rust;Bun 最早用 Zig 写,现在也切到了 Rust。但这并不是这次事件最有意思的部分。
真正有意思的是:当 AI 能参与百万行级别的跨语言迁移,软件开发的基本假设会发生什么变化?
一、技术背景:Bun 是什么?为什么 Bun 的创始人当初选了 Zig?
在深入这次迁移之前,我们需要理解 Bun 最初的技术选型。
Bun 是由 Jarred Sumner 从 2023 年开始构建的 JavaScript/TypeScript 全栈运行时。它的目标是用一个统一工具替代 Node.js、TypeScript 编译器、Bundler、Package Manager 和测试框架。Bun 内置了:
- HTTP Server 和 WebSocket
- SQLite、PostgreSQL 数据库驱动
bun install(比 npm 快一个数量级的包管理器).ts/.tsx/.jsx直接运行(无需 tsc 预处理)bun test测试框架- 文件系统 API 和 crypto 工具
最初选择 Zig 的核心理由是:Zig 提供了一种「有控制的 C 互操作」能力,同时避免了 C++ 的复杂度。Zig 的 comptime(编译期求值)、手动内存管理、裸指针操作,让 Bun 能够直接调用 WebKit 的 JavaScriptCore 引擎,而不需要任何额外的 FFI 抽象层。对于一个追求极致性能的 JS 运行时来说,这是一块完美的地基。
但随着时间推移,Bun 团队发现 Zig 代码库带来的内存 bug —— 释放后使用(use-after-free)、双重释放(double-free)、各类数据竞争 —— 消耗了大量调试时间。这成为最终迁移到 Rust 的核心动机。
二、迁移策略:不是「一键重写」,而是一条工程流水线
社区里有些人对 AI 重写的理解是:对 AI 说「帮我把整个项目重写」,然后等待奇迹发生。这当然不是 Bun 团队实际做的事。
Bun 团队实际部署的,是一套有严格约束的分阶段流水线,核心由三部分组成:
2.1 PORTING.md:迁移规则手册
在 PR #30412 合并前,仓库里就已经存在一份详细的 docs/PORTING.md 迁移指南,这是整个流水线的基础。这份文档定义了:
- 架构边界:哪些部分必须保持不变,哪些可以重新设计
- 类型映射规则:Zig 中的
*T(裸指针)映射到 Rust 时应该用什么类型 - Allocator 语义:Zig 的
Allocatortrait 在 Rust 中的等价物 - 禁止规则:不允许引入哪些第三方库,不允许改用 async Rust
- unsafe 边界:哪些场景允许使用 unsafe 绕过借用检查器
这份文档是整个迁移的核心产出。未来开发者最重要的产出会从「代码」变成「约束」 —— 当你为 AI 制定好迁移规则后,AI 才能安全地批量执行。
2.2 .claude/workflows:流水线分工
Bun 仓库里的 .claude/workflows 目录,是这个流水线的大脑。它将迁移任务分解为多个专门的工作流:
- Phase A:Zig → Rust 草稿翻译。目标不是通过编译,而是忠实地捕捉 Zig 代码的逻辑
- Phase B:按 crate 粒度解决编译错误、所有权问题、平台差异
- lifetime 分类:识别需要显式生命周期标注的代码段
- unsafe audit:专门审查每个 unsafe 代码块的安全边界
- Windows bug hunt:跨平台差异的专项修复
这不是一个通用 AI 助手的简单对话,而是一个多智能体协作系统,每个 Agent 负责特定阶段,输出供下一阶段消费的 artifact。
2.3 Phase A → Phase B:忠实翻译优先
关键的设计决策是:Phase A 的目标是「忠实移植」,而非「惯用 Rust」。
Jarred Sumner 明确说:「代码库整体上还是同样的架构、同样的数据结构。」这不是偷懒,而是务实的工程判断。如果在 Phase A 同时追求翻译正确性和 Rust 惯用法,任务会变得不可控。分阶段让 AI 先解决「对不对」,再解决「好不好」,降低了流水线的认知负荷。
三、重写的结果:数字背后是什么?
3.1 数字一览
| 指标 | 数值 |
|---|---|
| 提交次数 | 6755 次 |
| 涉及文件 | 2188 个 |
| 新增代码 | 100 万行以上 |
| 执行时间 | 9 天(几乎全部由 Claude Code 完成) |
| 测试通过率 | 99.8% |
| 二进制体积变化 | 缩小 3-8 MB(Linux x64 约 93 MB→85 MB) |
| unsafe 代码块 | 10000+ 个(分布在 700+ 文件) |
| 平台覆盖 | 全平台通过测试 |
3.2 99.8% 测试通过率:它证明的是什么?
测试通过率接近满分,是一件值得庆祝的事,但 dreamreal 在长文《Bun Has Been Converted to Rust. Now What?》中指出了一个重要问题:
测试通过率 99.8%,只能说明新实现与旧实现的行为一致。它不能说明新实现是安全的、更好的,甚至不能说明它是优秀的。
这个区分非常重要。测试套件验证的是外部可观测行为——API 返回值是否正确、HTTP 请求是否按预期处理、文件系统操作是否按规范执行。这些测试对内部实现的安全属性完全免疫。
3.3 10000+ 个 unsafe:被忠实翻译出来的内存管理债务
更值得深思的是:这次重写产生了超过 10000 个 unsafe 代码块,分布在 700 多个文件中。作为对比,同等体量的 Rust 项目 uv(Python 包管理器)只有 73 个 unsafe。
为什么会有这么多 unsafe?
答案藏在 Phase A 的策略里:忠实翻译 Zig 代码。
Zig 是手动内存管理语言。当一段 Zig 代码在 Rust 中无法通过借用检查器的严格规则时,Claude Code 按迁移规则「使用 unsafe 绕过」。这意味着那些在 Zig 中需要程序员手动保证正确性的代码,在 Rust 版本中仍然需要手动保证正确性——只是换了一种语言表达。
换句话说:忠实翻译出来的 Rust 代码,并不是惯用的内存安全 Rust 代码。 它是把 Zig 的手动内存管理「翻译」成了 Rust 的 unsafe 语法。
这是一个关于「代码迁移的本质」的深刻教训:
- 行为迁移:AI 非常擅长,可以达到 99.8% 的测试通过率
- 语义迁移:AI 的能力有限,「忠实」并不等于「正确地表达意图」
- 安全迁移:当前阶段几乎不可能由 AI 独立完成,需要大量人工审查
四、从 Zig 到 Rust:对 JS 生态意味着什么?
4.1 为什么 Rust 是 JS 运行时的「最终答案」?
Deno 选择了 Rust,Bun 也选择了 Rust。这不是巧合,而是 Rust 在这个特定领域的结构性优势:
1. 性能:Rust 的零成本抽象和无需 GC 的内存管理,让 JS 运行时可以在 C++ 的性能水平上运行,同时享受更现代的工具链。
2. 内存安全:WebAssembly 的崛起、AI Agent 的普及,使得「不依赖 GC」的系统语言成为云原生时代的基础设施首选。Rust 提供了这种安全性而无需引入垃圾回收暂停。
3. 跨平台能力:Rust 的交叉编译生态非常成熟,一套代码覆盖 Windows/macOS/Linux/Android/iOS,比 Zig 的工具链稳定得多。
4. 生态积累:Rust 在 WebAssembly (wasmtime, wasmer)、网络服务 (Tokio, Actix)、CLI 工具 (ripgrep, exa) 等领域都有大量经过生产验证的库。
4.2 不使用 async Rust:一个有意的保守决策
值得注意的是,Bun 团队明确表示「仍然没有改成 async Rust」。这是一个有意的设计决策,原因有三:
第一,Bun 的并发模型是基于 Worker Threads 的多线程架构,而不是事件循环的单线程模型。 Node.js 和 Deno 使用 libuv 的单线程事件循环,而 Bun 使用 std.Thread 的原生多线程。这种架构下,Tokio 风格的 async/await 模型并不天然适用。
第二,async Rust 的调试复杂度极高。 在生产环境中处理 async stacktrace、Pin、Future、Waker 的交互,已经让无数 Rust 开发者头疼。对于 Bun 这种底层运行时来说,减少认知负荷比引入最新范式更重要。
第三,迁移目标不是重构,而是换语言。 保持原有的多线程模型,让 Phase A 的「忠实翻译」策略更可行。如果同时改变并发范式,任务复杂度会指数级上升。
五、AI 重写基础设施:一个工程范式的转折点
5.1 软件开发的三层分工
Bun 这次迁移,最有价值的部分不是百万行代码本身,而是它揭示了AI 时代软件开发的新分层:
第一层:AI 负责吞吐
- 生成草稿、批量翻译、扫明显错误、跑自动验证
- 优势:速度极快、不知疲倦、可以并行
- 局限:无法独立判断语义正确性
第二层:工具负责约束
- 编译器、类型系统、lint 规则、测试套件、CI、benchmark
- 作用:尽可能在早期暴露问题,而不是等到生产环境
- 核心价值:将「好的」和「错的」区分开来
第三层:人负责判断
- 架构边界决策、发布节奏、兼容性判断、安全审计
- 最终责任仍然在人这里
- AI 不会替你承担生产事故
5.2 「试一试」的成本正在降低
在传统软件工程中,大型代码迁移(特别是跨语言迁移)之所以贵,是因为人力成本。以 Bun 百万行级别的代码量估算,即使 10 个人团队全职做,也需要数月到一年。
而 AI 的介入,把「试一次」的边际成本降到了接近零。Jarred Sumner 在公告中说:「我们好几个月没有亲手敲代码了。」这句话让很多人震惊,但真正重要的是它暗示的工程方法论转变:
很多以前因为人力不够而长期拖着的问题,现在可能被重新评估。团队会更愿意问:「如果 AI 能先生成 70% 的可审查草稿,我们是不是可以做这次迁移?」
5.3 静态约束的价值:在 AI 时代重新被发现
一个悖论出现了:AI 写代码越快,静态约束就越重要。
如果没有编译器、类型系统、lint 规则夹住 AI,AI 只是把错误生成得更快。Rust 的编译器在 Bun 的迁移中扮演了「守门人」角色——它强制要求每个 unsafe 代码块显式标注,强制要求每个借用检查失败都必须显式处理。
这与测试框架的分工完美契合:
- 编译器:捕获类型错误、借用检查、生命周期问题(静态分析)
- 测试套件:捕获行为偏差(动态验证)
- lint 和 clippy:捕获常见反模式和风格问题(代码质量)
三层防线共同构成了一个让 AI 安全工作的工程系统。
六、unsafe 审计:10000+ 块的债务如何消化?
6.1 为什么 unsafe 审计是真实的工程挑战?
Bun 团队留下 10000+ 个 unsafe 代码块,这是他们自己承认的现实问题。dreamreal 在分析文章中指出了一个关键事实:
Amazon 联合 Rust 基金会专门成立了社区项目来验证 Rust 标准库中的 unsafe 代码——而 Rust 标准库比 Bun 的运行时代码规模小得多,也经过了更严格的审查,且由人工编写。
这说明验证 unsafe Rust 本身就是一个尚未解决的学术难题。
当前的验证手段:
- Mirai(Facebook/Meta 开发):基于抽象解释的 Rust 程序分析工具,能检测部分 unsafe 错误
- Kani(AWS 开发):形式化验证工具,需要人工编写规格规范
- Miri:Rust 的解释器,能检测部分未定义行为,但只能处理单线程、小规模代码
这些都是半自动化工具,没有一个能一键完成「让这段 unsafe 代码变安全」的工作。
6.2 策略:按风险分层,逐步推进
对于 Bun 团队来说,合理的 unsafe 审计策略应该是按风险分层:
第一层(高风险优先):
- 直接操作系统资源的代码(文件系统、网络 I/O)
- 涉及指针运算和内存布局的代码(JavaScriptCore 绑定层)
- 涉及多线程共享状态的代码
第二层(中风险):
- 数据结构实现(Vec、HashMap 等容器)
- 字符串处理和编码转换
第三层(低风险,后续处理):
- 纯计算逻辑中的 unsafe 绕过
- 历史遗留的过度防御性 unsafe
这个分层策略意味着:即使 AI 生成了 10000+ 个 unsafe,也不是世界末日。关键是有清晰的优先级和逐步消化的路线图。
七、性能真相:实测 Bun Rust 版本的 benchmark
7.1 官方数据:持平到更快
Jarred Sumner 公布的 benchmark 数据显示,新版本与 Zig 版本相比「持平到更快」。这是个好消息,说明忠实翻译并没有让性能倒退。
但官方 benchmark 只是一般性参考。你的项目应该用真实工作负载测试,特别是关注:
- 冷启动时间:Bun 的招牌优势
- 依赖安装速度:
bun install的 npm 兼容性 - 构建速度:TypeScript 编译和 Bundling
- HTTP 吞吐量:每秒请求数
- 内存占用:RSS 和峰值内存
7.2 实战 benchmark 代码
以下是测试 Bun Rust 版本 HTTP Server 吞吐量的标准方法:
// benchmark-http.ts
// 运行方式: bun run benchmark-http.ts
import { serve } from "bun";
const PORT = 3000;
let requestCount = 0;
const startTime = Date.now();
const server = serve({
port: PORT,
async fetch(req) {
requestCount++;
return new Response("Hello from Bun!", {
headers: { "Content-Type": "text/plain" },
});
},
});
setInterval(() => {
const elapsed = (Date.now() - startTime) / 1000;
const rps = Math.round(requestCount / elapsed);
console.log(`Requests: ${requestCount}, Time: ${elapsed.toFixed(1)}s, RPS: ${rps}`);
requestCount = 0;
}, 1000);
console.log(`Bun HTTP Server running on port ${PORT}`);
// 用 wrk 压测
// wrk -t4 -c100 -d30s http://localhost:3000
# 安装 wrk(macOS)
brew install wrk
# 压测
wrk -t4 -c100 -d30s http://localhost:3000
# 对比 Node.js
# node server.js (需要 express: npm install express)
# wrk -t4 -c100 -d30s http://localhost:3000
测试 WebSocket 吞吐(模拟实时通信场景):
// benchmark-websocket.ts
import { serve } from "bun";
import type { ServerWebSocket } from "bun";
const PORT = 3001;
let connections = 0;
let messageCount = 0;
const server = serve({
port: PORT,
sockets: {
open(ws: ServerWebSocket) {
connections++;
},
message(ws: ServerWebSocket, message: string | Buffer) {
messageCount++;
// Echo back
ws.send(message);
},
close(ws: ServerWebSocket) {
connections--;
},
},
});
setInterval(() => {
const now = Date.now();
console.log(`[${now}] Connections: ${connections}, Messages/sec: ${messageCount}`);
messageCount = 0;
}, 1000);
console.log(`WebSocket Server on port ${PORT}`);
八、生产级实践:如何在你的项目中使用 Bun Rust 版本
8.1 升级到 Canary 渠道
Bun Rust 版本目前通过 canary 渠道分发。升级方式:
# 升级到 canary 版本
bun upgrade --canary
# 验证版本(Rust 版本会显示 commit hash)
bun --version
# 切回 stable
bun upgrade
8.2 数据库驱动测试(Bun 的 Rust 版本在 SQLite/PostgreSQL 方面表现稳定)
// test-database.ts
import { Database } from "bun:sqlite";
// SQLite 性能测试
const db = new Database("benchmark.db", { create: true });
// 批量写入测试
db.run("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, name TEXT, value REAL, created_at INTEGER)");
const insert = db.prepare("INSERT INTO events (name, value, created_at) VALUES (?, ?, ?)");
const startTime = Date.now();
const count = 100_000;
db.transaction(() => {
for (let i = 0; i < count; i++) {
insert.run(`event_${i}`, Math.random(), Date.now());
}
})();
const elapsed = (Date.now() - startTime);
console.log(`Inserted ${count} rows in ${elapsed}ms`);
console.log(`Rate: ${(count / elapsed * 1000).toFixed(0)} inserts/sec`);
// 查询测试
const query = db.prepare("SELECT COUNT(*) as cnt, AVG(value) as avg FROM events WHERE name LIKE ?");
const result = query.all("event_%");
console.log("Query result:", result);
// 清理
db.run("DROP TABLE events");
8.3 Bun FFI 调用 Rust 库(跨语言边界实战)
Bun Rust 版本为跨语言互操作提供了更好的稳定性。以下是在 Bun 中调用 Rust 编译的 .so/.dylib 的模式:
// 假设有一个 Rust 库编译为 libcrypto_ffi.dylib
// 使用 bun:ffi 调用
const lib = Bun.dlopen("./libcrypto_ffi.dylib", {
// 声明函数签名
generate_hash: {
parameters: ["cstring", "usize"],
returns: "cstring",
},
compute_merkle_root: {
parameters: ["buffer", "usize", "buffer"],
returns: "int",
},
} as const);
const input = "hello world from bun and rust";
const hash = lib.symbols.generate_hash(input, input.length);
console.log("Hash:", hash);
// buffer 操作
const data = new Uint8Array([1, 2, 3, 4, 5]);
const output = new Uint8Array(32);
const result = lib.symbols.compute_merkle_root(data, data.byteLength, output);
console.log("Merkle root result:", result, output);
Rust 侧(FFI 导出):
// lib.rs - 编译为 C ABI 动态库
// rustc --crate-type=cdylib -o libcrypto_ffi.dylib lib.rs
#[no_mangle]
pub extern "C" fn generate_hash(input: *const libc::c_char, len: usize) -> *mut libc::c_char {
let slice = unsafe { std::slice::from_raw_parts(input as *const u8, len) };
let text = String::from_utf8_lossy(slice);
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
text.hash(&mut hasher);
let hash = hasher.finish();
let result = format!("{:016x}", hash);
let boxed = Box::into_raw(result.into_boxed_str()) as *mut libc::c_char;
boxed
}
#[no_mangle]
pub extern "C" fn compute_merkle_root(
data: *const u8,
data_len: usize,
output: *mut u8
) -> libc::c_int {
if data.is_null() || output.is_null() {
return -1;
}
let slice = unsafe { std::slice::from_raw_parts(data, data_len) };
let hash = compute_double_sha256(slice);
unsafe {
std::ptr::copy_nonoverlapping(hash.as_ptr(), output, 32);
}
0
}
fn compute_double_sha256(data: &[u8]) -> [u8; 32] {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
data.hash(&mut hasher);
let h1 = hasher.finish().to_le_bytes();
let mut hasher2 = DefaultHasher::new();
h1.hash(&mut hasher2);
let h2 = hasher2.finish().to_le_bytes();
let mut result = [0u8; 32];
result[0..8].copy_from_slice(&h1);
result[8..16].copy_from_slice(&h2);
result
}
编译命令:
# 编译 Rust 库为 C ABI 动态库
rustc --crate-type=cdylib \
-C opt-level=3 \
-o libcrypto_ffi.dylib \
lib.rs
# macOS 下需要签名(如果运行时报错 "library not loadable")
# codesign -s - -f libcrypto_ffi.dylib
九、安全视角:10 年后看这次迁移
9.1 未定义行为的隐匿性
Hacker News 上最尖锐的问题是:「谁来审查这些代码?」坦率地说,以人类阅读速度去审查 AI 以光速生成的代码,本来就是不可能的任务。
未定义行为(Undefined Behavior)有一个关键特征:它不会通过测试失败来暴露自己。0.2% 未通过的测试,大多只是边缘场景和平台特定行为。而未定义行为更可能以另一种方式出现:
- 18 个月后,在某个从未运行过的 libc 版本上,被发现并登记为 CVE
- 某家路由器厂商恰好选用了某个行为古怪的 musl 版本,问题才第一次浮出水面
这不是危言耸听。Rust 标准库本身在数十年专家级别的审查下,也出现过 20+ 个可追溯到 unsafe 代码的 CVE。
9.2 Bun + Claude Code:AI 基础设施的新边界
这次迁移的最终意图更加深远:Anthropic 收购 Bun 的一个核心目标,是将 Bun 运行时集成进 Claude Code。当你在 Claude Code 中运行一个脚写项目时,底层的 JS 运行时可能就是 Rust 版的 Bun。
这意味着:每天处理数百万用户请求的基础设旒,将由 AI 编写、AI 维护。这是软件开发范式的根本性变化。
十、展望:Bun 的未来与 AI 基础设施的趋势
10.1 接下来会发生什么?
Bun 团队坦诚表示:「优化工作仍在进行中,最终版本发布前还会有一些清理工作。」对于用户来说,以下是可以预期的演进路径:
- 短期(2026 Q3):Rust 版本进入 stable 渠道,unsafe 数量开始有序下降
- 中期(2026 Q4-2027):Rust 惯用法逐步引入,减少 unsafe 依赖,提升安全性
- 长期(2027+):与 Claude Code 的深度集成,成为 AI 原生开发体验的核心组件
10.2 对整个行业的影响
Bun 的这次迁移给整个行业提供了一个公开的工程实验:
- 跨语言迁移的 AI 可行性:百万行级别可行,但需要严格的工程约束
- 「约束」作为核心资产:AI 时代,工程师最重要的产出会是规则和规格文档
- 测试套件的新定位:测试不只是 QA 工具,而是 AI 可验证的规格定义
- 三层工程范式:AI 吞吐 + 工具约束 + 人类判断,会成为未来大型工程的标准分工
结语
Bun 用 Rust 重写完成这件事,最有意思的地方不在于「Rust 赢了 Zig」,也不在于「AI 能不能写代码」。
最有意思的是:它让我们第一次看到了,当 AI 能参与大型代码迁移以后,软件开发会变成什么样。
三层分工的雏形已经出现——AI 负责吞吐,工具负责约束,人负责判断。但这并不意味着人类程序员会被取代。恰恰相反,它重新定义了人类程序员的核心价值:不是写代码的能力,而是设计约束、设计系统、评估风险的能力。
Jarred Sumner 说「我们好几个月没有亲手敲代码了」——但这不意味着 Bun 不需要人类工程师了。人类工程师从「写代码的人」变成了「设计让 AI 安全工作的工程系统的人」。这是同一份工作的升维,而不是工作的消失。
对于每一个正在使用或评估 Bun 的开发者来说,这次 Rust 重写的影响可以总结为三点:
- 性能不会变差:官方 benchmark 显示持平到更快
- 安全性会逐步提升:虽然目前 10000+ 个 unsafe,但 Rust 编译器已经提供了系统性约束框架
- AI 原生集成是最终目标:Bun + Claude Code 的结合,会重新定义「AI 辅助编程」的边界
现在 try 一下:
bun upgrade --canary
bun --version
看看你的下一个 Node.js 项目,是否应该迁移到 Bun。