编程 Bun 2.0 深度解析:从 Zig 到 Rust,Claude Code 如何用 9 天重写百万行代码

2026-07-10 13:14:56 +0800 CST views 5

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 的 Allocator trait 在 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 的这次迁移给整个行业提供了一个公开的工程实验:

  1. 跨语言迁移的 AI 可行性:百万行级别可行,但需要严格的工程约束
  2. 「约束」作为核心资产:AI 时代,工程师最重要的产出会是规则和规格文档
  3. 测试套件的新定位:测试不只是 QA 工具,而是 AI 可验证的规格定义
  4. 三层工程范式: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。

推荐文章

Go 开发中的热加载指南
2024-11-18 23:01:27 +0800 CST
Vue3中的v-slot指令有什么改变?
2024-11-18 07:32:50 +0800 CST
pin.gl是基于WebRTC的屏幕共享工具
2024-11-19 06:38:05 +0800 CST
25个实用的JavaScript单行代码片段
2024-11-18 04:59:49 +0800 CST
开发外贸客户的推荐网站
2024-11-17 04:44:05 +0800 CST
一个有趣的进度条
2024-11-19 09:56:04 +0800 CST
程序员茄子在线接单