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_freefs.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 的典型解决方案是:
- Arena 分配器:让分配的生命周期与某个明确的作用域绑定(例如解析器状态不会逃逸调用函数,AST 节点适合用 arena)
- 引用计数:手动管理引用计数
- "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 的所有权系统通过三个规则消除了大部分内存安全问题:
- 每个值都有一个所有者
- 同一时刻只能有一个所有者
- 所有者离开作用域时,值被自动释放
这意味着:
- 你不能同时有两个可变引用指向同一块内存(消除 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 版本安全得多。原因是:
- unsafe 的范围被显式标记:在 Zig 中,整个代码库都是"unsafe"的(没有任何编译器检查),而在 Rust 中,unsafe 只出现在与 C/C++ 交互的边界
- safe 代码部分仍然受编译器保护:即使有 13,000 个 unsafe,其余的代码仍然受所有权系统保护
- 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 天内持续运行。每个工作流都是一个循环:
- 生成移植指南:将 Zig 的模式和类型映射到 Rust 的模式和类型
- 机械移植:将每个
.zig文件转换为.rs文件,遵循 PORTING.md 和 LIFETIMES.tsv - 修复编译错误:逐个 crate 修复
- 让子命令工作:让
bun test、bun build等命令能正常运行 - 让测试通过:让 Bun 的整个测试套件通过
- 大型重构和清理:多个大规模的代码清理 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 官方博客中的技术声明提出了几点质疑:
性能提升归因于 LTO:Bun 官方博客将性能提升归因于 Link-Time Optimization,但 Andrew 指出 Zig 一直支持 LTO,而且之前因为 LLVM bug 而默认关闭了——这些 bug 同样影响 Rust。
模糊了"风格指南"和"语言特性"的边界:博客暗示你必须在"风格指南"和编程语言特性之间选择,但 Andrew 认为真正消除 bug 的方式是投入工程资源来修复它们(如 TigerBeetle 所做的),而不是换语言。
编译速度:Andrew 指出 Zig 编译器项目约 60 万行代码(与重写前的 Bun 规模相当),从零构建只需 16 秒,增量编译只需 90 毫秒。而 Bun 的 Rust 重写版本的编译速度如何?博客中没有提及。
5.4 关系的本质:价值观分歧而非技术之争
Andrew 总结道,Bun 和 Zig 之间的问题与语言特性无关,而与两个项目的价值体系分歧有关:
"Bun 和 Zig 之间的主要问题与 Zig 和 Rust 的语言特性无关,而与两个项目分叉的价值体系以及随之而来的关系破裂有关。"
第六章:技术深潜——Rust 重写后的架构变化
6.1 两个"不变"的关键设计
尽管语言从 Zig 换成了 Rust,Bun 重写后的架构保持了两个关键不变:
- 相同的架构设计:Bun 依然是一个"全能型"运行时,包含转译器、包管理器、测试运行器、HTTP 客户端等所有组件
- 相同的数据结构:底层的数据结构没有改变,只是用 Rust 重新实现了
这意味着重写不是"推倒重来",而是"在保持原有架构优势的基础上,用更安全的语言替换底层实现"。
6.2 极少的第三方依赖
Bun 依然使用极少的第三方库,依然不依赖 async Rust。这个决定在 Rust 社区中是异类的——大多数 Rust 项目会大量使用 tokio、hyper 等生态库。
Bun 不使用 async Rust 的原因与 Zig 版本相同:性能。异步运行时会引入额外的抽象层和内存分配,而 Bun 需要控制每一个内存分配的时机和方式。
6.3 unsafe 代码的治理策略
面对 13,000 个 unsafe 代码块,Bun 团队采取了以下策略:
- 逐步减少 unsafe:Jarred 在推文中表示 unsafe 数量已经从 13,000 下降了约 2,000,并预计会稳定在 10,000 左右
- SAFETY 注释:每个 unsafe 块都必须有 SAFETY 注释,解释为什么这里的 unsafe 是安全的
- 后续重构:在 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 版本的性能测试在各个平台上"达到或超越"了原有水平。性能提升主要归因于:
- Link-Time Optimization (LTO):Rust 的 LTO 支持更成熟
- 内存泄漏修复:修复了多个长期存在的内存泄漏,减少了 GC 压力
- 二进制体积优化:更小的二进制意味着更好的缓存命中率
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 用户
- 不要急于升级到 Rust 版本:Jarred 本人表示,优化工作仍在进行中,最终版本发布前还会有清理工作
- 使用 canary 频道测试:通过
bun upgrade --canary可以提前测试 Rust 版本 - 关注稳定性: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 重写开启了一个新的篇章。未来的路线图可能包括:
- 减少 unsafe 代码:逐步将 unsafe 代码重写为 safe Rust
- 引入 Rust 异步生态:虽然当前不使用 async Rust,但未来可能会逐步引入
- 性能优化:利用 Rust 的 zero-cost abstraction 进一步优化性能
- 社区重建:从 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 生成代码的信任?
- 人类开发者在这个新范式中的角色是什么?
这些问题没有简单的答案。但有一点是确定的:速度上天的时代,信任只能自己想办法落地。
参考资料
- Jarred Sumner, "Rewriting Bun in Rust", Bun Blog, July 8, 2026
- Andrew Kelley, "My Thoughts on the Bun Rust Rewrite", July 9, 2026
- Bun GitHub PR #30412, "Rewrite Bun in Rust"
- Bun v1.3.14 Release Notes
- Claude Code Issue #33453, "Memory leak in main process"
- GitHub Trending Repositories, August 2026
本文从第一性原理出发,深度拆解了 Bun 从 Zig 到 Rust 的重写过程。技术选型没有绝对的对错,只有适合不适合。Bun 的选择,折射出的是整个软件工程行业在 AI 时代面临的根本性问题:当机器能够以人类无法企及的速度完成重写时,我们该如何定义"质量"和"信任"?