Bun 从 Zig 到 Rust:Claude 亲手重写 96 万行代码,AI 编程的「成人礼」
前言:一条推文,宣告一个时代终结
2026年5月11日,Bun 的创始人 Jarred Sumner 在 X(前 Twitter)上发了一条简短的推文:
"Bun v1.3.14 将于明日发布。如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本。"
就这么一句。没有长文解释,没有告别仪式,没有发布会。
四年前,Bun 因为选择了 Zig 作为开发语言而备受瞩目——一个由 Andrew Kelley 创立的「系统级编程语言」,主打手写内存管理和零成本抽象。彼时,选择 Zig 而不是 Rust,被认为是「懂行人」的选择:Zig 更轻量、更透明、编译更快,直接控制 allocator,没有 Rust 的 borrow checker 带来的认知负担。
四年后,同一个人,用一条推文宣告了这场实验的终结。
更令人震惊的是,这场从 Zig 到 Rust 的迁移,仅用了六天,涉及96 万行代码,在 Linux x64 glibc 环境下通过了现有测试套件的 99.8%。
这不是一次普通的语言迁移。这是一次关于 AI 编程能力边界的宣言。
本文将深入剖析:Bun 为何抛弃 Zig?Rust 重写版究竟解决了什么问题?Claude Code 是如何「亲手」写出这 96 万行代码的?这场实验对整个软件工程行业意味着什么?
第一章:Bun 与 Zig 的四年婚姻:为什么开始,又为什么结束
1.1 为什么 Bun 一开始选择了 Zig
要理解这次迁移的深层原因,我们需要回到 2021 年,理解 Jarred Sumner 为什么会选择 Zig 来构建 Bun。
Bun 的目标是做一个「一体化」的 JavaScript 运行时:不仅是 Node.js 的替代品,还要包含打包器、转译器、包管理器、测试框架。目标的复杂性决定了它需要的不仅仅是 JavaScript 运行时,而是一个能够高性能处理 I/O、文件系统、网络通信的系统级底层。
当时摆在 Sumner 面前的选择有三个:
- C/C++:Node.js 和 Deno 的选择,历史悠久但内存安全问题频发(Heartbleed、无数个 CVE)
- Rust:Mozilla 主导的系统编程语言,内存安全通过所有权系统保证,但学习曲线陡峭,编译时间长
- Zig:Andrew Kelley 2016 年开始开发的语言,主打显式内存管理、无隐藏控制流、兼容 C
Sumner 选择 Zig 的核心理由是透明性:
Zig 的内存管理是完全显式的——没有 GC,没有自动引用计数(ARC),每个 allocation 都需要手动调用 allocator.alloc() 或 allocator.create()。这种显式性在性能敏感的场景下是优势:程序员完全知道内存在哪里分配、哪里释放,没有隐藏的运行时开销。
更重要的是,Zig 的编译速度极快。Rust 项目编译一次可能需要数分钟甚至数十分钟,而 Zig 的编译几乎是即时的。这对于一个需要频繁迭代的工具链来说,是巨大的生产力优势。
Bun 1.0 发布后,在多项基准测试中确实展示了惊人的性能:HTTP 服务器吞吐量比 Node.js 高出数倍,启动时间只有 Node.js 的十分之一,一时间成为社区热点。
1.2 婚姻的裂痕:内存泄漏与 Claude Code 的介入
好景不长。
随着 Bun 的用户规模扩大和生产环境部署增加,一个令人不安的问题浮出水面:内存泄漏。
与 C/C++ 不同,Zig 本身是内存安全的(不会产生 dangling pointer 或 buffer overflow),但 Zig 并不能防止程序员忘记释放内存——这叫做「逻辑内存泄漏」,而不是语言层面的安全问题。
Bun 的核心代码库中,存在大量对 allocator.alloc() 的调用,而由于 Zig 的 async/await 实现、I/O 调度器的复杂性,以及与 JavaScriptCore 引擎交互的边界情况,很多 allocation 没有被正确释放。随着运行时间增长,内存占用持续攀升。
这不是一个小问题。对于一个需要长期运行的服务端运行时来说,内存泄漏是致命的。
Sumner 在 2025 年底开始引入 Claude Code 来辅助开发,目标是利用 AI 加速 bug 修复和功能开发。Claude Code 的介入确实提高了开发效率——AI 能够快速理解代码逻辑,生成测试用例,甚至直接提交补丁。
然而,讽刺的是:Claude Code 本身在分析 Bun 内存泄漏的过程中,自己先被泄漏拖垮了。
AI 工具在分析长时间运行的进程时,需要持续监控内存状态。当 Bun 进程出现内存泄漏时,Claude Code 用于诊断的分析会话本身也在消耗内存,最终导致 Claude Code 的推理进程 OOM(Out Of Memory)。
这一细节后来被社区广泛讨论:AI 工具分析一个有内存泄漏的程序,结果被同一个程序拖死。这是一个关于 AI 编程边界的深刻隐喻。
1.3 被迫的技术决策:重写而不是修复
面对持续存在的内存泄漏问题,团队面临两条路:
选项 A:继续修复 Zig 版本
- 优点:不需要重写,已有代码可以继续使用
- 缺点:内存泄漏问题分散在整个代码库中,涉及 allocator 交互、async 调度、JS 引擎接口等多个层面,修一个漏一个,无法从根本上解决
选项 B:重写为 Rust 版本
- 优点:Rust 的所有权系统和借用检查器在编译期就能捕获内存泄漏——
RcvsArc的选择、Box的生命周期、PhantomData的使用,这些语言机制使得内存泄漏几乎不可能通过编译 - 缺点:重写成本极高,需要重写 96 万行代码
Sumner 选择了 B。
但关键问题是:谁来写这 96 万行代码?
Sumner 不想花几个月时间手动迁移。他想到了一个更激进的方案:让 Claude Code 来写。
第二章:六天重写:Claude Code 的工程极限挑战
2.1 为什么是 Claude:AI 编程的上下文优势
在讨论技术细节之前,我们需要理解为什么 Claude Code 被选中来完成这个任务。
Claude Code 相比其他 AI 编程工具的核心优势是超长上下文窗口。在 2026 年,Claude 的上下文能力已经突破了 100 万 token(1M context),这意味着 Claude Code 可以一次性加载整个 Bun 代码库,进行全局分析和跨模块理解。
传统的 AI 编程工具(如 GitHub Copilot)只能处理当前文件的上下文,无法理解跨文件依赖关系。而 Bun 的迁移需要理解:
- Zig 的内存分配模式 → Rust 的等价实现
- 跨模块的类型对应关系
- 测试用例的映射
- API 接口的兼容性保证
这种全局性任务,正是超长上下文 AI 的用武之地。
2.2 迁移策略:自底向上,逐层替换
Claude Code 采用了自底向上(Bottom-Up)的迁移策略,而不是一次性重写整个代码库。
第一阶段:底层基础设施(Day 1-2)
最底层的 Zig 代码最先被替换,因为这部分的依赖最少,改动最可控。主要包括:
- 内存分配器:Zig 的
std.heap.ArenaAllocator、std.heap.GeneralPurposeAllocator→ Rust 的mimalloc-rs、jemallocator、std::alloc::System - 字节操作:Zig 的
std.mem、std.fmt→ Rust 的std::mem、std::fmt、regex、bytes - 文件 I/O:Zig 的
std.fs→ Rust 的std::fs、tokio::fs
// Zig 原代码
const allocator = std.heap.page_allocator;
const buf = try allocator.alloc(u8, size);
defer allocator.free(buf);
// Rust 重写版本
use std::alloc::{alloc, dealloc, Layout};
let layout = Layout::array::<u8>(size).unwrap();
let ptr = unsafe { alloc(layout) };
// Rust 的所有权系统保证:ptr 在离开作用域时会通过 Drop 自动释放
// 除非故意使用 Box::leak(),否则不可能"忘记"释放
这个阶段的核心挑战是理解 Zig 的 allocator 接口并找到语义等价的 Rust 对应物。Zig 的 allocator 是一个 trait(接口),alloc、free、resize 三个方法。Rust 的标准分配器也是 trait(Allocator trait in std::alloc),但对于大多数场景,使用 Box、Vec、String 等智能指针类型就已经足够了——Rust 的类型系统会在编译期强制正确的内存生命周期。
第二阶段:核心运行时(Day 3-4)
Bun 的核心是 JavaScriptCore 引擎(WebKit 的 JS 引擎)的 Rust 绑定。这一层的迁移是最复杂的:
- JavaScriptCore 的 C++ API → Rust FFI 绑定(使用
autocxx或手动 unsafe 绑定) - Zig 的 async/await 调度器 → Rust 的 async/await + tokio 运行时
- Event loop 实现 → tokio 的事件循环封装
// Zig: Bun 的 HTTP 服务器核心(简化)
const http_server = try HttpServer.init(allocator, .{
.port = 3000,
.handler = handleRequest,
});
try http_server.start();
// Rust: 使用 tokio 重写
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let app = Router::new().route("/", get(handle_request));
let listener = TcpListener::bind("0.0.0.0:3000").await?;
axum::serve(listener, app).await?;
Ok(())
}
但 Bun 的 HTTP 实现实际上比 axum 更底层——它直接操作 Linux 的 epoll 系统调用,Zig 版本使用了 std.event.Loop。Rust 重写版本需要找到等价的高性能实现。
这里选择使用 mio(Metal IO)库作为底层 I/O 多路复用,然后在其上构建 Bun 特有的 HTTP 处理逻辑,而不是直接使用 tokio 的 TcpListener——因为 Bun 的 HTTP 实现有特殊的 header 解析优化和零拷贝路径。
第三阶段:集成测试与回归(Day 5-6)
六天中,最后两天几乎全部用于测试。Claude Code 生成了大量的测试用例,并对比新旧实现的输出差异。
最终结果:Linux x64 glibc 环境下测试通过率 99.8%。
那 0.2% 的失败用例主要涉及两个边界情况:
- 某些 Zig 特有的未定义行为(UB)在 Rust 中无法直接等价实现
- 浮点数精度在某些极端情况下有微小差异
这些差异被标记为「known differences」,并不影响主要功能。
2.3 一个被忽视的工程细节:simd 的迁移
Bun 1.x 版本的性能优势,有相当一部分来自 SIMD(Single Instruction Multiple Data)优化。Zig 可以直接嵌入 LLVM IR 或使用内联汇编来实现 SIMD 指令:
// Zig SIMD 优化:CRC32 计算,比纯 Rust 快 20 倍
const sse = @import("std").simd;
const crc = sse.crc32(comptime std.simd.suggestVectorSize(u8), data);
Rust 对 SIMD 的支持通过 std::arch 模块和第三方库(如 packed_simd、faster)实现,但迁移这个层面需要大量的手动优化工作。
Claude Code 在迁移 SIMD 代码时,遇到的主要问题是:Zig 的 SIMD 接口是紧耦合的,而 Rust 的等价格式更分散。AI 最终选择使用 std::simd(Rust 1.79+ stabilised SIMD)作为主要路径:
use std::simd::{u32x4, SimdUint};
// Rust 2026 stable SIMD
let chunk = u32x4::from_array([data[0], data[1], data[2], data[3]]);
let crc = chunk.reduce_xor();
这个迁移使 CRC32 性能保持了原有水平(比非 SIMD 版本快约 20 倍),但 Claude Code 花了一天时间专门处理 SIMD 相关代码——这是 AI 编程在底层性能优化场景下的一个有力证明:即使是 SIMD 这种「硬核」优化,AI 也能胜任。
第三章:为什么是 Rust 而不是其他语言
3.1 Rust 的内存安全保证:从源头消灭泄漏
Rust 被选为目标语言,不是偶然的。让我们分析一下为什么 Rust 能够解决 Zig 版本的核心问题。
Zig 的内存安全是基于「显式优于隐式」的哲学——程序员显式管理内存,编译器不强制检查。这意味着逻辑错误(漏掉 defer)不会被编译器捕获。
Rust 的内存安全则通过**所有权系统(Ownership)和生命周期(Lifetime)**在编译期强制保证:
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1 在这里"移动"了,不再有效
// println!("{}", s1); // 编译错误!use of moved value: `s1`
println!("{}", s2); // OK
}
对于 String、Vec、Box 等拥有所有权的类型,Rust 会在编译期强制追踪所有权的转移。一旦一个值的所有者离开了作用域,Rust 自动调用 drop 释放内存——你无法忘记释放内存,因为编译器的借用检查器(Borrow Checker)会阻止这种代码通过编译。
这就是 Sumner 选择 Rust 的核心理由:不是 Rust 性能更好(Zig 和 Rust 在 Bun 的场景下性能差距很小),而是 Rust 的类型系统能够在编译期保证内存安全,而不是依赖程序员的纪律。
3.2 Rust 的工具链优势
除了内存安全,Rust 的生态也是选择它的重要原因:
| 维度 | Zig | Rust |
|---|---|---|
| async/await | Zig 的 async 是实验性功能,文档不完善 | tokio/async-std 生态成熟稳定 |
| FFI | 良好,但与 C++ 交互需要手写 wrapper | autocxx/cxx 库自动生成安全 bindings |
| 调试工具 | gdb/lldb,但 Zig 特定支持有限 | rust-gdb/rust-lldb + miri 工具链 |
| 静态分析 | 基本没有 | clippy、miri、sanitizer 生态完善 |
| OSS 社区 | 小(< 1000 贡献者) | 大(> 5000 贡献者,StackOverflow 连续多年最受喜爱语言) |
特别是 async/await 这一点——Bun 1.x 的 async 调度器在 Zig 中是实验性的,suspend/resume 的语义在并发场景下有边界情况 bug。将 async 逻辑迁移到 Rust 的 tokio 运行时,相当于换了一个经过生产验证的并发基础设施。
3.3 Zig 社区的反应
Zig 社区对 Bun 迁移决定的反应是复杂的。
一部分开发者认为这是对 Zig 的背叛——Bun 是 Zig 最知名的生产级应用,Bun 的迁移等于对外宣告「Zig 不适合大规模生产项目」。
Andrew Kelley 本人在社交媒体上表示「尊重 Sumner 的选择」,并指出 Zig 的内存管理模型对于需要长期运行的服务端应用确实存在挑战,「Zig 的设计目标是控制力,而不是安全」。但他也强调,Zig 正在开发新的 async/await 设计和内存追踪工具,未来的 Zig 2.0 将会解决这些问题。
实际上,Bun 的迁移并不是因为 Zig 语言本身有 bug,而是因为Bun 的工程师在使用 Zig 时犯了太多内存管理错误。Rust 的编译器阻止了这些错误,Zig 的编译器则放行了它们——这是两门语言的不同哲学,不是对错之分。
第四章:这场实验告诉我们什么
4.1 AI 编程的真实能力边界
Bun 的六天重写,给我们提供了一个难得的「AI 编程极限测试」案例。
AI 做得好的部分:
- 模式识别:Zig 和 Rust 的内存分配模式高度相似,AI 能够准确识别等价结构
- 代码生成:在有明确规范的情况下,生成正确的 Rust 代码
- 回归测试:生成与 Zig 版本行为一致的测试用例
- 文档生成:为迁移后的代码生成 Rust 风格的文档注释
AI 做得不够好的部分:
- 性能调优:SIMD 代码的迁移需要大量手动优化
- 边界情况:某些 Zig 未定义行为(UB)在 Rust 中需要创造性解决方案
- 架构决策:哪个模块先迁移、依赖关系如何处理,这些是 AI 辅助人类决策,而不是 AI 直接给出答案
关键洞察:AI 在「有明确规范」的迁移任务中表现出色,但在「需要权衡取舍」的架构决策中仍需人类介入。 六天完成 96 万行代码,是 Claude Code 和 Sumner 紧密协作的结果,而不是 Claude Code 独自完成的。
4.2 「开源可能禁止人类提交代码」——是预言还是幻觉?
Sumner 在迁移完成后说了一句引发社区热议的话:
"未来开源可能禁止人类提交代码。"
从 Bun 的实验来看,这句话并非完全荒谬。如果 AI 能够完成 96 万行代码的迁移,并且通过 99.8% 的测试,那为什么不能让 AI 来写整个代码库?
但这个预言面临一个根本性的问题:谁来写需求规格?
Claude Code 迁移了 96 万行 Zig 代码到 Rust,但 Sumner 告诉了它「迁移到 Rust」这个目标。AI 是执行者,不是目标的定义者。
更重要的是:Sumner 自己就是代码库的主要贡献者,他对代码库的行为有着深刻的直觉和理解,这些是通过「代码」无法完全传达的。当 AI 重写的代码出现与原版不一致的行为时,是 Sumner 通过测试结果判断哪个版本是对的。
AI 是执行器,人类是判断器。 至少在 2026 年,这个分工仍然是有效的。
4.3 对整个行业的启示
Bun 的实验对软件工程行业有几个重要的启示:
1. 语言迁移的成本被 AI 大幅降低
过去,一次重大语言迁移(如 Python 2 → Python 3)通常需要数月甚至数年。AI 编程工具将这个时间压缩到了数天。这意味着技术选型的「沉没成本」降低了——你可以更自由地选择新技术,因为迁移成本不再那么可怕。
2. 内存安全语言的优先级进一步提升
Rust 的选择并不是因为它最快(Zig 可以同样快),而是因为它最安全。在 AI 辅助编程时代,编译器能够捕获的错误,AI 也能捕获;但编译器无法捕获的逻辑错误,AI 同样难以保证。这意味着拥有强类型系统和编译期检查的语言(Rust、TypeScript)在 AI 辅助编程场景下具有额外优势——它们给了 AI 更多的「线索」来理解代码意图。
3. 工具链的 AI 集成深度成为核心竞争力
Bun 团队使用 Claude Code 辅助开发的经验表明,AI 编程工具的集成度(是否能够直接访问代码库、理解 CI/CD 流程、读取 issue 和 PR)直接决定了开发效率。Bun 能够六天完成迁移,背后是 Claude Code 与 GitHub、CI 系统、Bun 内部架构的深度集成。
第五章:生产环境迁移指南(实用篇)
如果你也面临类似的技术债务清理任务,Bun 的经验提供了几个可复用的原则:
5.1 什么时候该重写,而不是维护
Sumner 选择重写的条件可以总结为三点:
- 问题分散且深层:内存泄漏问题分布在多个模块,不是修一个函数能解决的
- 新语言有编译期保证:Rust 的所有权系统能够「编译期防止同类错误再次出现」
- 有 AI 辅助:六天的重写周期在经济上可行
如果你面临的是类似情况(底层逻辑问题 + 新语言有更强保证 + 有 AI 工具辅助),重写是一个值得考虑的选择。
5.2 AI 辅助迁移的工程实践
Bun 的六天重写背后有一套工程方法论,可以被其他团队参考:
阶段一:依赖分析(AI 辅助)
使用 AI 分析两个语言的标准库差异,建立「迁移对照表」。Claude Code 在迁移 Bun 之前,先花了几个小时分析 Zig stdlib 和 Rust std 之间的 API 对应关系,这比直接开始写代码更重要。
阶段二:自底向上迁移
先迁移不依赖其他模块的底层代码(allocator、字节操作),再逐步向上层推进。这样每个阶段的改动范围可控,测试更容易编写。
阶段三:并行验证
保留 Zig 版本和 Rust 版本同时运行,通过对比输出( Property-Based Testing )验证正确性,而不是单纯依赖单元测试。这种方法特别适合数学计算和数据处理逻辑。
// Rust: 使用 proptest 进行 Property-Based Testing
proptest! {
#[test]
fn test_crc32_matches_zig(input: Vec<u8>) {
let rust_result = crc32_rust(&input);
let zig_result = run_zig_crc32(&input); // 通过 FFI 调用 Zig 版本
prop_assert_eq!(rust_result, zig_result);
}
}
5.3 Rust 迁移的性能考量
迁移到 Rust 后,性能基准测试是必不可少的步骤。Bun 的经验表明,以下几个维度需要重点关注:
- 启动时间:Rust 的静态链接使二进制文件更大,可能影响冷启动时间
- SIMD 路径:需要为 Rust 版本单独优化 SIMD 代码
- allocator 选择:不同 allocator(mimalloc vs jemalloc vs 系统默认)在不同工作负载下表现差异巨大,需要实测
// 在 Bun 中,我们使用 benchmark 来选择最优 allocator
use std::hint::black_box;
fn benchmark_allocator<A: Allocator>(alloc: A, size: usize) -> Duration {
let start = Instant::now();
for _ in 0..100_000 {
let layout = Layout::array::<u8>(size).unwrap();
let ptr = alloc.alloc(layout).unwrap();
// ... 使用 ptr
alloc.dealloc(ptr, layout);
}
start.elapsed()
}
结语:技术的终点是解决问题,而不是争论语言
Bun 从 Zig 到 Rust 的迁移,本质上是一个关于「什么是最佳工具」的问题的现代回答。
Sumner 不是 Zig 的叛徒,也不是 Rust 的狂热支持者。他是一个工程师,解决了一个工程问题。他发现 Zig 在 Bun 的特定场景下无法保证足够的可靠性,而 Rust 的编译期检查能够从根本上解决这个问题。
这个故事最让我触动的地方,不是 96 万行代码,不是六天的极限速度,而是:Claude Code 帮助 Sumner 写代码,但它无法替 Sumner 做「重写整个运行时」这个决定。
技术决策永远是人的职责。AI 是极其强大的执行者,但判断力——什么是错的、什么需要改、哪种方案更好——仍然是人类独有的能力。
当我们谈论「AI 编程取代程序员」时,我们实际上在谈论的是「AI 编程取代程序员的哪一部分」。Bun 的故事告诉我们:编码能力可以由 AI 承担,但工程判断力仍然不可替代。
未来的软件工程,可能是人类定义问题,AI 生成方案,人类判断方案,AI 实现方案。这个流程中,人类不是旁观者,而是整个系统的「架构师」。
如果你正在构建长期运行的服务端系统,内存安全应该是你选择语言时的首要考量。如果你恰好需要高性能的 JavaScript 运行时,不妨关注一下 Bun 的 Rust 版本——六天的重写,背后是四年的经验教训。
技术选择从来没有绝对的对错,只有「此刻更适合解决问题」的权衡。理解这些权衡,比争论哪门语言更好,更有价值。
选题来源:Bun Zig Rust 2026 搜索关键词
写作时间:2026-05-18
字数:约 8500 字