编程 Bun 深度拆解:从 Zig 到 Rust 的 6755 次 Commit——一个 JavaScript 运行时如何用 AI 重写自己

2026-08-02 22:14:12

Bun 深度拆解:从 Zig 到 Rust 的 6755 次 Commit——一个 JavaScript 运行时如何用 AI 重写自己

引言:当一个运行时决定背叛自己的母语

2026 年 5 月 11 日,Bun 创始人 Jarred Sumner 在 X 上发了一条推文,语气平静得像在说今天天气不错:

"Bun v1.3.14 将于明日发布。如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本。"

就这么一句话。四年前,Bun 因为选择了 Zig 而显得特立独行——一个 JavaScript 运行时不用 C++,不用 Rust,偏偏选了一个几乎没人听说过的系统语言。四年后,Zig 版本被它的创造者用一条推文宣告了终结。

这不是一次普通的语言迁移。这是一场由 AI 驱动的、涉及 6755 个 commit、超过 100 万行代码的运行时重写。更荒诞的是,重写 Bun 的正是 Claude——那个运行在 Bun 之上的 AI 编程助手。Claude Code 被 Bun 的内存泄漏拖垮了,然后 Anthropic 让 Claude 去重写 Bun,最后 Bun 再继续支撑 Claude Code。

这个闭环,堪称 2026 年软件工程最魔幻的叙事。

本文将从第一性原理出发,深度拆解这次重写的技术细节:为什么 Zig 的内存管理模型在 Bun 的场景下失灵了?Rust 的所有权系统如何解决了问题?Claude Code 是如何在 6 天内完成 96 万行代码迁移的?以及,当 13,000 个 unsafe 代码块出现在一个号称"内存安全"的项目中时,我们该信任什么?


第一章:Bun 的前世今生——从 esbuild 的 Zig 移植到 JavaScript 运行时巨头

1.1 起点:一行 Zig 代码改变一切

2021 年 4 月 16 日,Jarred Sumner 在 Zig 的 GitHub 上提交了第一个 issue。在此之前,他刚刚在 Hacker News 上看到了 Zig 的单页语言参考文档,被那种对底层控制的极致追求所震撼。

Bun 的第一个版本,实际上是 esbuild 的 JavaScript/TypeScript 转译器从 Go 到 Zig 的逐行移植。Jarred 在奥克兰一间狭小的公寓里,用一年时间,在没有 LLM 辅助的情况下,从零构建了 Bun 的雏形。

这个起点决定了 Bun 的基因:它从第一天起就是一个"scope 疯狂膨胀"的项目。JavaScript、TypeScript、CSS 转译器和压缩器;npm 兼容的包管理器;类 Jest 测试运行器;Node.js 兼容的模块解析;HTTP/1.1 和 WebSocket 客户端;几十个 Node.js API 的重新实现——所有这些,都被塞进了一个二进制文件。

1.2 Zig 的选择:为什么不是 C++,不是 Rust?

Zig 的核心卖点是"没有隐藏控制流"。没有构造函数、没有析构函数、没有运算符重载、没有隐式类型转换。一切清理代码都通过显式的 defer 和 errdefer 关键字来执行。

对于 Bun 这种需要极致性能的项目,Zig 的优势是明显的:

  • 编译速度快:Zig 编译器本身就是用 Zig 写的,自举性好
  • C 互操作零开销:Zig 可以直接调用 C 函数,不需要 FFI 绑定层
  • 可控的内存分配:每个内存分配都可以精确追踪生命周期
  • comptime 编译期执行:可以在编译期运行任意代码,减少运行时开销

Bun 的创始人 Jarred 在官方博客中坦言:

"Zig 让 Bun 成为可能。如果不是 Zig,我不可能在一年内构建出这么多东西。"

1.3 巅峰与隐患:2200 万月下载量背后的 4700 个 Issue

到 2026 年初,Bun 的 CLI 已经达到每月超过 2200 万次下载。Claude Code、OpenCode 等 AI 编程工具选择 Bun 作为运行时。Vercel、Railway、DigitalOcean 等平台提供了一等公民级别的 Bun 支持。

但与此同时,Bun 的 GitHub 上积累了约 4700 个未解决的 issue——而几乎"驱动整个互联网"的 Node.js,同期只有约 1700 个。

波兰数字会员系统公司 Rewardo 的 CTO Wojciech Maj 曾做过一个对比:

"Node.js 承担着全球级别的工作负载,却维持着更小的 backlog;而仍处于早期阶段的 Bun,却已经被问题淹没了。"

更致命的是,这些问题中有一大类反复出现:内存泄漏和 use-after-free 崩溃。


第二章:内存之殇——为什么 Zig 的 defer 在 Bun 的场景下不够用

2.1 GC 与手动内存管理的碰撞

这是 Bun 面临的根本性技术矛盾:JavaScript 是一门带垃圾回收的语言,但 Zig 是一门不提供内存管理的系统语言。

现代 JavaScript 引擎(如 JavaScriptCore 和 V8)对异常处理和垃圾回收有严格的规则。而 Zig,像 C 一样,不替你管理内存。这意味着 Bun 团队必须同时处理两套内存模型:

  • GC 管理的内存:JavaScript 对象、闭包、Promise 等
  • 手动管理的内存:C/C++ 库的内部状态、网络缓冲区、文件句柄等

Jarred 在官方博客中写道:

"正确处理垃圾回收值和手动管理值的生命周期,一直是 Bun 稳定性问题的主要来源——通常是小的内存泄漏,偶尔是崩溃。每一次内存分配都必须被仔细审查。这些字节在哪里被释放?如何确保它只被释放一次?我们是否正确检查了 JavaScript 异常?这个垃圾回收指针是否对保守栈扫描器可见?这是垃圾回收内存还是手动管理内存?"

2.2 一份触目惊心的 Bug 清单

在 Bun v1.3.14 的发布说明中,官方列出了一份令人不安的 bug 清单。这些不是边缘场景的理论问题,而是真实影响用户的生产级 bug:

use-after-free 类:

  • node:zlib 中调用 .reset() 时,如果异步 .write() 仍在 threadpool 上执行,会导致 use-after-free 崩溃
  • node:http2 中可重入 JS 回调(例如在 timeout 监听器中调用 session.request())触发 hashmap 重哈希,导致内部流指针失效
  • UDPSocket.send() 和 sendMany() 中,用户代码在 valueOf() 或 toString() 回调中可能在载荷捕获和实际发送之间分离 ArrayBuffer

内存泄漏类:

  • crypto.scrypt 中输出缓冲区分配失败时,回调和受保护的 password/salt 缓冲区永远不会被释放
  • tlsSocket.setSession() 每次调用泄漏一个 SSL_SESSION(约 6.5KB),因为 d2i_SSL_SESSION 后缺少 SSL_SESSION_free
  • fs.watch() 的 watcher 在 .close() 后永远不会被垃圾回收,因为引用计数下溢导致每个 watcher 都被固定为 GC root

其他类:

  • CSS 解析器中 background-clip 带浏览器前缀和多层背景时的 double-free 崩溃
  • DuplexUpgradeContext 从未被释放——每次 tls.connect({ socket: duplex }) 都泄漏一个完整对象
  • MessageEvent 中的竞态条件崩溃,GC 标记线程在并发访问期间观察到 m_data 中的 torn variant

2.3 defer 的局限性:当清理代码需要"恰好执行一次"

Zig 的 defer 关键字在离开作用域时执行清理代码。这在大多数场景下足够好,但对于 Bun 这种需要同时管理 GC 和手动内存的场景,问题就来了:

fn processRequest(socket: *TCPSocket) !void {
    const buffer = try allocator.alloc(u8, 4096);
    defer allocator.free(buffer);  // 离开作用域时释放

    const response = try handleRequest(socket, buffer);
    defer response.deinit();  // 离开作用域时清理

    try socket.send(response.data);
    // 如果这里抛出异常,defer 会正确执行
    // 但如果 socket.send 内部又调用了 JS 回调,
    // 回调中可能访问已经被 defer 标记为待释放的 buffer
}

核心问题在于:defer 知道什么时候释放,但不知道"谁还在引用这块内存"。当同一个指针被传递给多个函数,而某些函数在调用后仍然引用该指针时,defer 就无法安全地释放它。

Zig 的典型解决方案是:

  1. Arena 分配器:让分配的生命周期与某个明确的作用域绑定(例如解析器状态不会逃逸调用函数,AST 节点适合用 arena)
  2. 引用计数:手动管理引用计数
  3. "pay really close attention":瞪大眼睛仔细审查

方案 3 在 Bun 这种规模的项目中显然是不可持续的。

2.4 Zig 的"no hidden control flow"哲学 vs Bun 的现实

Zig 的设计理念是"没有隐藏控制流"。这意味着:

  • 没有 C++ 的隐式析构函数 ~Destructor
  • 没有 Rust 的隐式 Drop
  • 清理代码必须在每个调用点显式写出

这种设计在很多项目中是优点——你能精确知道每一行代码在做什么。但对于 Bun 这种需要同时处理 GC 和手动内存的场景,它变成了一个负担。

如果用 Zig 实现类似 Rust 的智能指针,代码会变成这样:

fn foo(a_ptr: SharedPtr(TCPSocket)) !void {
    const a: *TCPSocket = a_ptr.get();
    defer a_ptr.deref();

    const b = try do_something_with_a(a);
    defer b.deref();

    // ...
}

而 Bun 期望的 Zig 代码是这样的:

fn foo(a: *TCPSocket) !void {
    const b = try do_something_with_a(a);
    // ...
}

前者虽然可行,但失去了 Zig 简洁优雅的本色。Jarred 坦言:

"自制的智能指针提供了比 Rust 更差的 ergonomics,却没有任何保证。我不想这么做。"


第三章:为什么是 Rust——从 13,000 个 unsafe 到编译器保证

3.1 Rust 的杀手锏:编译器替你检查内存安全

Rust 的核心价值不在于"没有 unsafe",而在于它让你必须显式标记 unsafe 代码,并通过所有权系统在编译时捕获大部分内存错误。

对于 Bun 之前反复出现的 use-after-free、double-free 和"忘记在错误路径释放"等问题,Rust 的安全子集可以直接编译为编译错误:

fn process_request(socket: &mut TCPSocket) -> Result<(), Error> {
    let buffer = vec![0u8; 4096];  // 自动管理内存

    let response = handle_request(socket, &buffer)?;
    // response 在这里有效

    socket.send(&response.data)?;
    // buffer 和 response 在函数结束时自动释放
    // 如果中途 return Err,Rust 也会自动清理
    Ok(())
}

在 Rust 中,你不需要手动写 defer,不需要担心"某个回调还在引用这块内存"。编译器会确保在任何代码路径下,内存都只被释放一次,且在释放后不再被访问。

3.2 RAII 与 Drop:自动清理的正确打开方式

Rust 的 Drop trait 提供了 RAII(Resource Acquisition Is Initialization)语义。当一个值离开作用域时,它的 drop 方法会被自动调用:

struct Buffer {
    data: Vec<u8>,
}

impl Drop for Buffer {
    fn drop(&mut self) {
        println!("Buffer with {} bytes freed", self.data.len());
    }
}

fn example() {
    let buf = Buffer { data: vec![0; 4096] };
    // 使用 buf 做各种事情...
    // buf 在这里自动释放,不需要手动 defer
}

这与 Zig 的 defer 的区别在于:Drop 是类型系统的一部分,而不是程序员的纪律。你不会忘记写 Drop,因为 Rust 编译器会要求你处理它。

3.3 所有权系统:从"pay really close attention"到"编译器帮你盯着"

Rust 的所有权系统通过三个规则消除了大部分内存安全问题:

  1. 每个值都有一个所有者
  2. 同一时刻只能有一个所有者
  3. 所有者离开作用域时,值被自动释放

这意味着:

  • 你不能同时有两个可变引用指向同一块内存(消除 data race)
  • 你不能在值被释放后继续使用它(消除 use-after-free)
  • 你不能释放已经被释放的内存(消除 double-free)

对于 Bun 这种需要同时处理 GC 和手动内存的场景,Rust 的所有权系统提供了一个清晰的边界:

// JavaScript 对象由 GC 管理
struct JsValue {
    inner: *mut JSC_JSGlobalContext,  // GC 管理的指针
}

// C/C++ 库的内存由 Rust 的所有权系统管理
struct HttpConnection {
    socket: OwnedFd,  // Rust 管理文件描述符的生命周期
    buffer: Vec<u8>,  // Rust 管理缓冲区的生命周期
}

// 两者的交互通过 unsafe 显式标记
unsafe fn js_value_to_buffer(val: &JsValue) -> &[u8] {
    // 这里的 unsafe 表示"我在做编译器无法验证的事情"
    // 但 unsafe 的范围被严格限制在这个函数内
}

3.4 代价:13,000 个 unsafe

但 Rust 不是银弹。Bun 需要与大量底层 C/C++ 代码交互——JavaScriptCore 引擎、uWebSockets/usockets HTTP 服务器、lshpack/lsquic HTTP/3 库、BoringSSL、SQLite。这些交互都必须通过 unsafe 来实现。

开发者 Theo(t3.gg 创始人)在 X 上抛出了一个让 Jarred 不得不正视的对比:

"uv 包含 35 万行 Rust 代码,以及 73 个 unsafe 调用。Bun Rust 移植版已经有 68.1 万行 Rust 代码,并且有超过 13,000 个 unsafe 调用。"

Jarred 几乎立刻回应:

"今天已经下降了大约 2000。我预计它会稳定在 1 万左右,因为 Bun 的大部分内容都是用 C 和 C++ 编写的,这种情况不会改变。"

这个数字引发了一个关键问题:如果一个"内存安全"的重写包含了 13,000 个 unsafe 代码块,它的安全性提升到底有多大?

答案是:仍然比 Zig 版本安全得多。原因是:

  1. unsafe 的范围被显式标记:在 Zig 中,整个代码库都是"unsafe"的(没有任何编译器检查),而在 Rust 中,unsafe 只出现在与 C/C++ 交互的边界
  2. safe 代码部分仍然受编译器保护:即使有 13,000 个 unsafe,其余的代码仍然受所有权系统保护
  3. unsafe 代码更容易审查:你可以专门审查这 13,000 个 unsafe 块,而不是审查整个代码库

第四章:AI 重写的工程学——Claude Code 如何在 6 天内重写 96 万行代码

4.1 为什么是"全部一次重写"而非"增量迁移"

传统的语言迁移策略是增量式的:逐步将模块从旧语言迁移到新语言,在过渡期保持两个版本并存。但 Jarred 选择了截然不同的路径——一次性全部重写。

他在官方博客中解释了原因:

"在我的经验中(从 esbuild 的 Go 到 Zig 的移植),一次性全部重写比增量重写更好。增量重写会添加你希望最终删除的临时代码,而且在短期和中期都会很痛苦。"

这个判断在 Bun 的场景下尤其正确:Bun 的测试套件是用 TypeScript 写的,不依赖运行时的编程语言。这意味着你可以用同一套测试来验证 Rust 版本是否与 Zig 版本行为一致。

4.2 PORTING.md:一份 576 行的迁移指南

在开始重写之前,Bun 团队编写了一份极其详细的 PORTING.md 文档——一份 576 行的 Zig-to-Rust 迁移指南。这份文档将迁移分为两个阶段:

Phase A(机械移植):

  • 逐文件忠实保留 Zig 的逻辑
  • 即使 Rust 代码暂时不能编译也没关系
  • 关键是"语义投影"——把 Zig 的意思翻译成 Rust

Phase B(编译修复):

  • 逐个 crate 解决编译、构建和运行问题
  • 确保测试套件通过

文档还规定了极其具体的规则:

  • 文件命名规则:如何将 .zig 文件映射为 .rs 文件
  • crate 引用规则:如何组织 Rust 的模块结构
  • 禁止使用的库:禁止使用 tokio、rayon、hyper、futures
  • 禁止 async fn:保持与 Zig 版本相同的异步模型
  • unsafe 必须写明 SAFETY 注释:每个 unsafe 块都必须解释为什么这里是安全的
  • 遇到不确定逻辑时留下 TODO:不要让 AI 自行猜测

4.3 50 个动态工作流:AI 写代码的正确打开方式

Jarred 在官方博客中详细描述了他如何使用 Claude Code 来执行这次重写。核心方法论是:

# 伪代码,不是真实代码
task = todo_list.pop()
result = task()
feedback = await Promise.all([review(result), review(result)])
await apply(feedback, result)

他使用了约 50 个动态工作流,在 11 天内持续运行。每个工作流都是一个循环:

  1. 生成移植指南:将 Zig 的模式和类型映射到 Rust 的模式和类型
  2. 机械移植:将每个 .zig 文件转换为 .rs 文件,遵循 PORTING.md 和 LIFETIMES.tsv
  3. 修复编译错误:逐个 crate 修复
  4. 让子命令工作:让 bun test、bun build 等命令能正常运行
  5. 让测试通过:让 Bun 的整个测试套件通过
  6. 大型重构和清理:多个大规模的代码清理 pass

Jarred 强调,这不是简单地告诉 Claude "重写 Bun 为 Rust,别出错"然后祈祷它能工作。他花了大量时间监控工作流的输出,手动检查问题和 bug,然后提示 Claude 编辑工作流来修复问题。

4.4 从 4000 个 commit 到 6755 个 commit:时间线

2026 年 5 月初:Bun 仓库出现 claude/phase-a-port 分支,数十万行 AI 生成的 Rust 代码与原始 Zig 实现并排存在。

5 月 7 日:Jarred 发推称 Rust 迁移已经涉及约 4000 次 commit、96 万行代码,当时只剩下 3 个编译错误。Rust 版本已经能显示 help menu,bun run 和 package.json scripts 也已经跑起来。

5 月 9 日:Jarred 宣布 Rust 重写版本已经在 Linux x64 glibc 环境下通过了 Bun 既有测试套件的 99.8%。

5 月 11 日:Jarred 发出那条引爆社区的推文:"如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本。"

5 月 14 日:Bun 正式宣布核心运行时已从 Zig 重写为 Rust,包含 6755 个 commit。

4.5 GitHub 被"干爆":100 万行代码的 PR

这个 PR(#30412)包含超过 100 万行新增代码,直接导致 GitHub 页面无法加载。这可能是 GitHub 历史上最大的单个 PR 之一。

Jarred 在博客中坦言,他最初并不认为 AI 能做到这件事:

"我没有想到技术已经成熟到可以完成这个壮举。但他做到了,现在我正在比喻性地从一个写着'味道就像不再是我的问题了'的杯子里喝着美味的茶。"


第五章:Andrew Kelley 的回应——Zig 创始人的视角

5.1 一段复杂的关系

2026 年 7 月 9 日,Zig 语言创始人 Andrew Kelley 发表了一篇题为"My Thoughts on the Bun Rust Rewrite"的博客文章。这篇文章不仅是一篇技术评论,更是一段关于开源项目之间关系破裂的叙事。

Andrew 将 Jarred 描述为一个具有强烈"初学者能量"的人——行动迅速,大量尝试,但工程产出常常是中等水平。这种特质在学习阶段是健康的,但在领导一个 VC 支持的创业公司时就变成了问题。

5.2 代码质量之争

Andrew 指出,Zig 团队经常检查用户项目的源代码,以了解语言如何影响用户。当他们检查 Bun 的代码库时,看到了令人不安的实践:

"Hacks on top of hacks。滥用断言。最重要的是,鲁莽地冲过一个又一个功能,几乎没有花时间反思和消除 bug 及技术债。Jarred 在能够使用 LLM 之前就已经在写 slop 了。"

他特别提到了 Bun 团队对 Zig 社区严格"no-AI policy"的态度。Zig 基金会成员 Loris Cro 曾公开表示,大量 LLM 贡献只会制造"幻觉 PR"、"垃圾噪音"以及动辄上万行、根本无法维护的提交。

5.3 对 Rust 重写的技术批评

Andrew 对 Bun 官方博客中的技术声明提出了几点质疑:

  1. 性能提升归因于 LTO:Bun 官方博客将性能提升归因于 Link-Time Optimization,但 Andrew 指出 Zig 一直支持 LTO,而且之前因为 LLVM bug 而默认关闭了——这些 bug 同样影响 Rust。

  2. 模糊了"风格指南"和"语言特性"的边界:博客暗示你必须在"风格指南"和编程语言特性之间选择,但 Andrew 认为真正消除 bug 的方式是投入工程资源来修复它们(如 TigerBeetle 所做的),而不是换语言。

  3. 编译速度:Andrew 指出 Zig 编译器项目约 60 万行代码(与重写前的 Bun 规模相当),从零构建只需 16 秒,增量编译只需 90 毫秒。而 Bun 的 Rust 重写版本的编译速度如何?博客中没有提及。

5.4 关系的本质:价值观分歧而非技术之争

Andrew 总结道,Bun 和 Zig 之间的问题与语言特性无关,而与两个项目的价值体系分歧有关:

"Bun 和 Zig 之间的主要问题与 Zig 和 Rust 的语言特性无关,而与两个项目分叉的价值体系以及随之而来的关系破裂有关。"


第六章:技术深潜——Rust 重写后的架构变化

6.1 两个"不变"的关键设计

尽管语言从 Zig 换成了 Rust,Bun 重写后的架构保持了两个关键不变:

  1. 相同的架构设计:Bun 依然是一个"全能型"运行时,包含转译器、包管理器、测试运行器、HTTP 客户端等所有组件
  2. 相同的数据结构:底层的数据结构没有改变,只是用 Rust 重新实现了

这意味着重写不是"推倒重来",而是"在保持原有架构优势的基础上,用更安全的语言替换底层实现"。

6.2 极少的第三方依赖

Bun 依然使用极少的第三方库,依然不依赖 async Rust。这个决定在 Rust 社区中是异类的——大多数 Rust 项目会大量使用 tokio、hyper 等生态库。

Bun 不使用 async Rust 的原因与 Zig 版本相同:性能。异步运行时会引入额外的抽象层和内存分配,而 Bun 需要控制每一个内存分配的时机和方式。

6.3 unsafe 代码的治理策略

面对 13,000 个 unsafe 代码块,Bun 团队采取了以下策略:

  1. 逐步减少 unsafe:Jarred 在推文中表示 unsafe 数量已经从 13,000 下降了约 2,000,并预计会稳定在 10,000 左右
  2. SAFETY 注释:每个 unsafe 块都必须有 SAFETY 注释,解释为什么这里的 unsafe 是安全的
  3. 后续重构:在 Bun v1.4 发布后,团队计划逐步重构代码,使其更像"idiomatic Rust"

6.4 二进制体积的变化

重写后的 Bun 二进制文件体积缩小了 3-8 MB。Jarred 在博客中解释,这些体积优化与语言重写无关——它是团队在重写过程中做的工程工作,而这些工作本应早就在 Zig 代码库中完成。

Andrew Kelley 对此评论道:

"博客中关于'二进制体积'的部分,恰恰说明了为什么博客花了这么长时间才出来——你们在做那些本应在 Zig 代码库中从一开始就做的工程工作。"


第七章:更宏大的叙事——AI 重写软件的大趋势

7.1 不只是 Bun

Bun 的重写不是孤例。类似的 AI 驱动极限重写正在多个领域同时发生:

  • Cloudflare 在一周内借助 AI 重新实现了 Next.js API 的大部分能力
  • Ladybird 浏览器 在两周内将自己的 JavaScript 引擎从 C++ 迁移到了 Rust

7.2 Jarred 的预言

Jarred 自己在 5 月 3 日发过一条推文:

"这种 pipeline,任何 VC 支持的 OSS 或者有大量 GitHub issues 的公司都能搭建。更普遍地说,它可以用于自动修复用户报告的 bug。"

他甚至更早预言过:

"我预计开源软件会走向完全相反的方向——未来甚至可能变成'禁止人类贡献代码'。人类依然会负责讨论问题、决定优先级,但真正写代码、提交 PR、回复和处理反馈、完成实现的工作,最终都会由 LLM 来完成。"

7.3 "Vibecoded Disaster" 之争

Bun 的重写引发了关于 AI 生成代码质量的激烈讨论。开发者社区的核心担忧是:

流程问题:uv 的 Rust 代码是由人类开发人员编写的,每一行都经过了审查。而 Bun 的 Rust 代码由 AI 编写、AI 审核、AI 批准和合并。

安全问题:6 天、96 万行、AI 生成、AI 测试,最后带着 1 万个 unsafe 直接合并——这种流程是否足够安全?

Jarred 回应了"被迫重写"的猜测:

"没人逼我这么做。"

但社区的担忧并未消散。

7.4 信任问题

当你的 CTO 说"我们要把代码库从 X 语言重写成 Y 语言"时,他不会再问"需要几个月",而是会问"Claude 需要几天"。

但速度上天的时代,信任只能自己想办法落地。


第八章:性能对比——Zig 版 vs Rust 版

8.1 官方数据

Bun 官方博客声称,Rust 版本的性能测试在各个平台上"达到或超越"了原有水平。性能提升主要归因于:

  1. Link-Time Optimization (LTO):Rust 的 LTO 支持更成熟
  2. 内存泄漏修复:修复了多个长期存在的内存泄漏,减少了 GC 压力
  3. 二进制体积优化:更小的二进制意味着更好的缓存命中率

8.2 编译速度的争议

Andrew Kelley 指出了一个被 Bun 官方博客忽略的关键指标:编译速度。

Zig 编译器项目约 60 万行代码:

  • 从零构建:16 秒
  • 增量编译:90 毫秒

Bun 的 Rust 重写版本约 68 万行代码:

  • 编译速度未知(博客中未提及)

这个对比暗示了一个潜在的问题:如果 Bun 的编译速度显著慢于 Zig 版本,开发者体验可能会下降。

8.3 内存占用的改善

最显著的改善来自内存占用。之前 Claude Code 的主进程 RSS 内存在约 3 小时的会话中从约 1.7GB 增长到 14GB 以上——泄漏位于 Bun 运行时的 WebKit Malloc 分配器中。

修复后的版本应该能显著改善这个问题,因为 Rust 的所有权系统在编译时就能防止大部分内存泄漏。


第九章:对开发者的影响——你该怎么做

9.1 如果你是 Bun 用户

  1. 不要急于升级到 Rust 版本:Jarred 本人表示,优化工作仍在进行中,最终版本发布前还会有清理工作
  2. 使用 canary 频道测试:通过 bun upgrade --canary 可以提前测试 Rust 版本
  3. 关注稳定性:Rust 版本已经通过了 99.8% 的测试套件,但仍有 0.2% 的差异需要关注

9.2 如果你在评估运行时

Bun 的重写提出了一个重要的评估维度:当你选择一个运行时时,你选择的不只是它的当前状态,还有它的技术债务。

Bun 的 4700 个未解决 issue 是一个警示信号。即使 Rust 重写解决了内存安全问题,其他问题(如 Node.js API 兼容性、边缘场景的稳定性等)仍然需要时间来解决。

9.3 如果你在考虑 AI 辅助重写

Bun 的案例提供了一个有价值的参考:

可行的方面:

  • AI 能够以人类无法企及的速度完成跨语言迁移(6 天 vs 3 周)
  • 语言无关的测试套件是验证重写正确性的关键
  • 详细的迁移指南(PORTING.md)是成功的基础

需要警惕的方面:

  • unsafe 代码的审查不能完全交给 AI
  • 代码质量需要人类的判断和品味
  • "通过测试"不等于"生产就绪"

第十章:总结与展望——Bun 的未来与 JavaScript 生态的变局

10.1 Bun 的未来路径

Bun 的 Rust 重写开启了一个新的篇章。未来的路线图可能包括:

  1. 减少 unsafe 代码:逐步将 unsafe 代码重写为 safe Rust
  2. 引入 Rust 异步生态:虽然当前不使用 async Rust,但未来可能会逐步引入
  3. 性能优化:利用 Rust 的 zero-cost abstraction 进一步优化性能
  4. 社区重建:从 Zig 社区的"前明星"转变为 Rust 社区的新成员

10.2 JavaScript 生态的三足鼎立

Bun 的重写改变了 JavaScript 运行时的竞争格局:

  • Node.js:C++ + V8,最成熟、最稳定的运行时
  • Deno:Rust + V8,注重安全性和 TypeScript 支持
  • Bun:Rust + JavaScriptCore,注重性能和"全能型"体验

三者的技术栈差异正在缩小——Deno 和 Bun 都使用 Rust,Node.js 也在逐步引入 Rust 组件(如新的测试运行器)。未来的竞争将更多地围绕生态、工具链和开发者体验展开。

10.3 AI 驱动的软件工程新范式

Bun 的案例标志着一个新时代的到来:AI 不再只是辅助编程的工具,而是能够独立完成大规模工程任务的参与者。

但这也带来了新的挑战:

  • 如何确保 AI 生成代码的质量?
  • 如何建立对 AI 生成代码的信任?
  • 人类开发者在这个新范式中的角色是什么?

这些问题没有简单的答案。但有一点是确定的:速度上天的时代,信任只能自己想办法落地。


参考资料

  1. Jarred Sumner, "Rewriting Bun in Rust", Bun Blog, July 8, 2026
  2. Andrew Kelley, "My Thoughts on the Bun Rust Rewrite", July 9, 2026
  3. Bun GitHub PR #30412, "Rewrite Bun in Rust"
  4. Bun v1.3.14 Release Notes
  5. Claude Code Issue #33453, "Memory leak in main process"
  6. GitHub Trending Repositories, August 2026

本文从第一性原理出发,深度拆解了 Bun 从 Zig 到 Rust 的重写过程。技术选型没有绝对的对错,只有适合不适合。Bun 的选择,折射出的是整个软件工程行业在 AI 时代面临的根本性问题:当机器能够以人类无法企及的速度完成重写时,我们该如何定义"质量"和"信任"?

推荐文章

程序员茄子在线接单