编程 Bun 从 Zig 到 Rust:11天、53万行代码、64个Claude——一次重构整个软件工程认知的极限实验

2026-07-23 20:44:17 +0800 CST views 5

Bun 从 Zig 到 Rust:11天、53万行代码、64个Claude——一次重构整个软件工程认知的极限实验

前言:一条推文宣告一个时代

2026年5月11日,Jarred Sumner 在 X 上发了一条极其简短的推文:

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

就这么一句话。四年前,Bun 因为选择了 Zig 作为核心实现语言而显得特立独行——在 Node.js 用 C++、Deno 用 Rust 的格局中,Bun 是唯一一个押注 Zig 的顶级项目。四年后,Zig 版本被它的创造者用一条推文宣告了终结。

但真正让整个技术社区沸腾的,不是"换语言"这件事本身,而是完成这件事的方式:11天,53万行代码,64个 Claude 实例并行工作,总成本 16.5 万美元,最后零测试被删除、全部测试通过,二进制体积减少 20%,整体性能提升 5%。

这不是一次正常的技术决策,这是一次对"软件开发到底需要多久"这个问题的极限挑战。而挑战的结果,正在重新定义我们对 AI 写代码这件事的认知上限。

本文从工程师视角,对这次事件进行一次完整的技术解剖:Zig 为什么走到了这一步?Rust 重写的架构是怎样的?Claude 生成的代码质量到底如何?以及——这对整个软件工程行业意味着什么。


一、背景:为什么是 Zig,为什么是 Bun

要理解这次重构的深层意义,我们需要先理解 Bun 为什么从一开始选择了 Zig。

1.1 Zig 的设计哲学与性能承诺

Zig 由 Andrew Kelley 于 2016 年创建,核心理念是"做 C 的现代替代品"。它的几个关键特性吸引了 Bun 的创始人:

显式内存管理,无垃圾回收。 Zig 没有 GC,这对需要极致性能的运行时来说至关重要。JavaScript 引擎(V8/SpiderMonkey)本身就有自己的内存管理,再叠一层 GC 会引入不可控的暂停。

编译期计算与 comptime。 Zig 的 comptime 允许代码在编译时执行,这意味着可以在编译阶段完成大量静态分析和优化,运行时零开销。

与 C 库的无缝互操作。 Zig 可以直接调用 C 头文件,不需要 wrapper,这让它成为嵌入 WebKit/JavaScriptCore 等 C/C++ 代码库的天然选择。

没有隐藏的控制流。 相对于 C++ 的 RAII 析构函数和异常机制,Zig 的 defer 和 error union 让控制流完全显式,更容易推理。

Bun 最初选择 Zig,正是看中了这些特性——做一个比 Node.js 更快的运行时,同时保持对现有生态(npm 包、CommonJS/ESM)的完整兼容。

1.2 四年之痒:积累的问题开始爆发

然而,四年的生产运营也暴露了 Zig 的深层问题:

内存泄漏。 这是最致命的问题。JavaScriptCore 的 FFI 接口与 Zig 的内存管理之间存在边界模糊地带,特别是在 event loop 和异步任务调度层面。大量用户报告 Bun 在长时间运行后内存持续增长,最终占用数 GB。

语言稳定性不足。 Zig 的 API 变更频率极高——很多 breaking change 直接来自标准库重构。对于一个需要维护 53 万行代码的项目来说,这意味着持续的技术债务和升级成本。

构建系统的不确定性。 Zig 的自举过程(bootstrap)和构建工具链变化频繁。不同 Zig 版本之间的编译器行为差异,有时会导致难以解释的编译错误。

社区文化的局限性。 Zig 社区对 AI 生成代码持坚决反对态度,官方政策是"禁止 AI 生成 issue、PR 或代码评论"。当 AI coding 成为行业主流工具时,这种立场让 Zig 生态在某些方面显得格格不入。

调试困难。 当 Zig 代码与 JavaScriptCore 深度集成后出问题,调试栈往往跨越 Zig → C → JavaScriptCore 多层边界,定位 root cause 异常困难。

这些问题在 2025 年底 Anthropic 收购 Bun 后被进一步放大——因为 Bun 已经成为 Claude Code 的核心依赖,而 Claude Code 本身的稳定性问题最终被追踪到了 Bun 的内存泄漏。


二、导火索:Claude Code 的 14GB 内存灾难

2026年初,Claude Code 用户开始密集反馈一个令人崩溃的问题:长时间会话后,Claude Code 的内存占用从初始的 1.7GB 暴涨到 14GB,甚至更高。

GitHub Issue #33453 是最具代表性的记录:

"Claude Code 的主进程表现出严重的内存泄漏,RSS 内存在约 3 小时的短会话中从约 1.7GB 增长到 14GB 以上。泄漏位于 Bun 运行时的 WebKit Malloc 分配器中,而非用户空间的 JavaScript 分配。"

另一份被自动关闭的 Issue #11377 更夸张:运行 14 小时后,Claude Code 进程占用 23GB 虚拟内存,143.8% CPU 使用率,系统完全卡死。

问题的本质是:Bun 深度嵌入到了 Claude Code 的执行链路中。Claude Code 以 Bun 可执行文件的形式分发,这意味着 Claude Code 的每一个进程都运行在 Bun runtime 上。Bun 的任何不稳定性,都会直接映射到 Claude Code 的用户体验上。

Claude Code 负责人 Boris Cherney 曾解释过为什么 Claude Code 最终选择了 Bun:

"我们当初在开发 Claude Code 时,评估了很多运行时方案,Bun 几乎是毫无悬念的胜者。它的启动时间大概只有 3 毫秒,而 Python 要慢 15 倍左右。对于 CLI 工具来说,这意味着用户体验是'丝滑响应',还是'明显卡顿'。"

但"快"和"稳定"是两回事。当 Claude Code 每次长会话都以系统卡死告终时,这个"3 毫秒启动"的优势就变得毫无意义。


三、重写策略:Phase A + Phase B 的双轨迁移

2026年5月初,Bun 团队启动了一个内部项目,代号 claude/phase-a-port。整个迁移策略被写成了一份详细的 PORTING.md 文档——长达 576 行,将迁移拆分为两个严格阶段:

Phase A:语义投影(Semantic Projection)

Phase A 的核心原则是忠实保留 Zig 代码的逻辑,即使 Rust 代码暂时无法编译也没关系。这个阶段的目标是"语义等价",而非"代码优雅"。

关键约束:

  • 逐文件翻译:Claude 拿到一个 Zig 文件,输出语义上完全等价的 Rust 代码
  • 禁止异步重构:Phase A 禁止使用 async fntokiorayonhyperfutures 等异步生态库
  • unsafe 必须标注 SAFETY 注释:每个 unsafe 块必须写清楚为什么这段代码是安全的
  • 遇到不确定逻辑时写 TODO:宁可留一个待处理标记,也不要让 Claude 自行猜测语义
  • 禁止使用高级 Rust 抽象:在 Phase A 中,trait 对象、泛型推导等高级特性被限制使用

Phase A 的设计哲学是:先让代码跑起来,再让代码跑得对,最后让代码跑得好。 这是一个三步走的工程策略,而不是一步到位的理想主义。

Phase B:编译修复与优化

Phase B 才是真正"让 Rust 代码变成 Rust"的阶段。Phase B 的目标:

  • 逐个 crate 解决编译错误
  • 用 Rust 的类型系统和生命周期检查替换 Zig 的等价物
  • 逐步引入 Rust 生态的工具链(异步运行时、并发原语)
  • 性能优化与内存布局调整

四、关键数据:数字背后的工程量

让我们直接看这次重写的硬数字:

指标数据
总耗时约 11 天
Claude 实例并行数最高 64 个
Zig 代码总量约 53 万行
Rust 代码总量约 67 万行(Rust 更冗长)
Commit 数量超过 6755 个
测试套件通过率(Linux x64 glibc)99.8%
二进制体积变化减少 20%(3-8 MB)
整体性能提升约 5%
总成本约 16.5 万美元
修复的内存泄漏数多个长期存在的泄漏
修复的 flaky 测试数多个间歇性失败测试

这些数字中,最值得关注的是测试通过率 99.8%整体性能提升 5%

99.8% 的测试通过率意味着:Rust 重写版本在 Linux 平台上已经"足够正确",足以支撑生产环境。而性能提升 5% 则说明,Rust 的控制能力在某些关键路径上甚至超过了 Zig——这对于一个 JavaScript 运行时来说,是一个不小的成就。


五、技术深潜:Zig 到 Rust 的迁移难点

5.1 Event Loop 与 Task 调度

Event loop 是 JavaScript 运行时的核心。Zig 版的 Bun 使用了 tagged pointer 技术来实现 event loop task:

// Zig 版的 event loop task(简化)
const Task = struct {
    tag: u2,           // 2 bits足够区分4种task类型
    ptr: [*]u8,        // 指向实际数据的指针
    callback: *const fn(*Task) void,
};

pub fn schedule(task: *Task) void {
    // 将 tag 和指针打包成一个 usize
    const packed = (@as(usize, @intFromEnum(task.tag)) << 63) | @intFromPtr(task.ptr);
    io_uring_submit_single(packed);
}

这种技术在 Zig 中是零开销的,因为 Zig 的 comptime 和直接内存操作让它变得简单。但移植到 Rust 时,Rust 的类型系统不接受"把两个东西硬塞进一个 usize"的hack——Rust 鼓励你使用枚举或结构体,让类型系统来保证正确性。

然而,如果直接用 Box<Task>Arc<Task>,会引入额外的内存分配和引用计数开销。Bun 团队最终在 Rust 中用了一个折中方案:

// Rust 版的 event loop task(概念模型)
pub enum Task {
    IoUring(IoUringTask),
    ProcessExit(ProcessExitTask),
    NonBlockingIo(NonBlockingIoTask),
    Timer(TimerTask),
}

struct IoUringTask {
    data: *mut u8,
    // 不再打包,直接用 Rust 的 enum 变体
}

impl Task {
    // unsafe 是必要的,但必须写清楚 SAFETY 契约
    unsafe fn schedule(self: Box<Self>) {
        // SAFETY: caller must guarantee the task data is valid
        // and not aliased until the completion callback fires
        io_uring_queue_push(&self.data);
    }
}

Rust 的 enum + box 模式比 Zig 的 tagged pointer 更安全(不会因为指针运算错误导致 UB),但需要额外的间接层。最终通过精心设计的内存布局和 Placement New,把这个开销压到了可接受范围。

5.2 JavaScriptCore FFI 集成

Bun 的 JavaScript 引擎是 JavaScriptCore(JSC),苹果 Safari 用的引擎。相比 V8,JSC 的优势是更紧凑、更省内存,但它的 C++ API 非常复杂,与 Rust 的互操作更是充满挑战。

// Zig 版的 JSC 值创建(简化)
pub fn createString(allocator: *Allocator, vm: *JSVirtualMachine, str: []const u8) !*JSCValue {
    const result = try allocator.create(JSCValue);
    result.* = .{ .vm = vm };
    // Zig 可以直接调用 C 构造函数
    result.handle = JSC_JSValueMakeFromUTF8String(vm.handle, str.ptr, str.len);
    return result;
}
// Rust 版的 JSC 值创建(简化)
use jsc_bindings::*;

// 关键:Rust 中没有隐式的 C 互操作,需要显式声明 extern "C"
// 每个 FFI 调用都是 unsafe 的
pub fn create_string(allocator: &mut Allocator, vm: &JSVirtualMachine, s: &[u8]) -> Result<Pin<Box<JscValue>>> {
    let mut result = Box::pin(JscValue::new(allocator));
    
    // SAFETY: 
    // - allocator must be valid and non-aliasing for this value
    // - the string bytes must be UTF-8 encoded
    // - vm must outlive this JscValue
    unsafe {
        result.handle = JSC_JSValueMakeFromUTF8String(vm.as_ptr(), s.as_ptr(), s.len());
    }
    
    Ok(result)
}

Rust 版本的每个 FFI 调用都需要写 SAFETY 注释,这看起来繁琐,但实际上是一种强制性的文档。在 Zig 版本中,很多隐含的假设从未被明确写出;在 Rust 版本中,每个不变量都必须被显式声明。

5.3 文件系统操作与 io_uring

Bun 的高性能 I/O 大量依赖 Linux 的 io_uring 接口。Zig 对 io_uring 的封装是手写的,性能极高。Rust 生态中 tokio-uring 是最接近的选择,但 Bun 的 Phase A 策略禁止使用 tokio。

这意味着 Rust 版本的 io_uring 封装需要从头手写,或者直接复用 Zig 版的 C 封装:

// Rust 版的 io_uring 封装(Phase B 阶段)
use std::io::{self, Read, Write};

// 直接复用 Zig 编译出的 C 兼容对象文件
extern "C" {
    fn bun_io_uring_submit(
        sqe: *mut io_uring_sqe,
        fd: i32,
        opcode: u8,
        addr: u64,
        len: u32,
    ) -> i32;
    
    fn bun_io_uring_wait_cqe() -> *mut io_uring_cqe;
}

// SAFETY: 
// - io_uring must be initialized and in a valid state
// - sqe must point to a valid Submission Queue Entry
// - fd must be a valid file descriptor
#[inline(always)]
pub unsafe fn submit_read(fd: RawFd, buf: &mut [u8], offset: u64) -> io::Result<usize> {
    let sqe = get_next_sqe();
    bun_io_uring_submit(sqe, fd, IORING_OP_READ, buf.as_mut_ptr() as u64, buf.len() as u32);
    let cqe = bun_io_uring_wait_cqe();
    parse_cqe_result(cqe)
}

最终,Rust 版本通过复用 Zig 编译出的 C 对象文件,实现了零重写——只写 Rust 的封装层,底层的 io_uring 逻辑直接复用已有的优化版本。这是一种务实的工程选择:不要为了"纯 Rust"而重写已经证明正确的底层代码。


六、争议与批评:Andrew Kelley 的反击

当 Bun 宣布 Rust 重写完成的消息时,最激烈的批评来自 Zig 的创造者 Andrew Kelley。

在 Create 42 的访谈中,Kelley 将这次重写定性为 "unreviewed slop"(未经审核的垃圾代码):

"六天写出来的 53 万行代码,你不可能真正审核过它。这不是软件开发,这是代码雨。"

他的核心批评点:

1. 代码质量无法保证。 53 万行代码在 11 天内生成,即便有 99.8% 的测试通过率,测试也只能证明"行为一致",无法证明"逻辑正确"。测试覆盖不了的地方,可能藏着难以发现的 bug。

2. unsafe 的泛滥。 Theo(t3.gg 创始人)的数据引发了广泛关注:Bun Rust 版本有超过 13,000 个 unsafe 调用,而 uv(同样是 Rust 项目)只有 73 个。unsafe 是 Rust 内存安全模型的"漏洞",大量 unsafe 意味着大量潜在的未定义行为。

3. 缺少人类判断。 AI 生成代码时没有"架构直觉"——它不会考虑"这段代码两年后还能不能被理解"。人类的代码审核不仅仅是找 bug,还包括代码的可维护性、API 的设计合理性、命名的一致性等。

4. 破坏开源协作规范。 在 Zig 社区,AI 生成代码被认为是"噪音"——它污染 PR 历史、浪费审核者的时间、引入难以维护的风格不一致。当 Bun 用 AI 生成 53 万行 Rust 代码时,它实际上是在向整个 Rust 社区倾倒技术债务。


七、反驳与辩护:为什么这次重写是可行的

面对 Kelley 的批评,Jarred Sumner 和支持者们给出了自己的辩护:

测试覆盖率足够。 Bun 的测试套件包含数万个测试用例,覆盖了 JavaScript 执行、npm 包兼容、网络 I/O、文件系统、bundler、test runner 等所有核心功能。99.8% 的通过率意味着几乎所有功能行为都得到了验证。

Rust 编译器本身就是最好的静态分析工具。 虽然 unsafe 调用多,但 Rust 的 borrow checker 依然在结构层面保证了很多不变量。Zig 没有等价的工具链——Zig 编译器信任程序员,但不强制任何内存安全约束。

性能证明了架构选择。 5% 的性能提升说明 Rust 版本在关键路径上的优化是成功的。如果代码质量真的很差,性能应该是下降的,而不是提升。

解决了实际问题。 多个长期存在的内存泄漏被修复了。多个 flaky 测试不再 flaky。这些问题在 Zig 版本中存在了数年,始终无法根治——这不是 AI 的问题,而是 Zig 语言在某些领域的能力边界问题。

Jarred 本人的原话也许是最好的总结:

"我真的很厌倦为内存泄漏、崩溃和稳定性问题而担忧和花费大量时间进行修复。如果编程语言能提供更强大的工具来预防这些问题,那就太好了。"


八、AI 重写软件的大趋势:Bun 不是孤例

Bun 的这次 11 天重写,仅仅是 2026 年 AI 驱动代码迁移浪潮中的一个缩影。

Cloudflare:一周内借助 AI 重新实现了 Next.js API 的大部分能力。

Ladybird 浏览器:两周内将 JavaScript 引擎从 C++ 迁移到了 Rust。

TypeScript 团队:Go 重写编译器,一年完成,10 倍性能提升。

Bun 本身的这次重写:11 天,53 万行 Zig → Rust,测试全部通过。

这些案例有一个共同模式:当 AI 被给予明确的语义映射规则(Phase A),它可以在极短时间内完成人类需要数月甚至数年才能完成的大规模代码迁移。

但也有共同的问题:

项目迁移耗时人工审核比例核心争议
TypeScript Go 重写~1年高(核心团队)迁移期间功能冻结
Bun Zig→Rust11天极低unsafe 泛滥、缺少 review
Ladybird JS→Rust2周API 兼容性妥协

Bun 是这三种模式中"走得最远"的——它的人工审核比例最低,争议也最大。但同时,它也是速度最快、成果最直接的。


九、软件开发的新范式:从"人月神话"到"代码工厂"

Bun 的创始人 Jarred Sumner 在更早的时候曾说过一句预言:

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

这次重写,正是这句话的第一次大规模公开演练。

但我们必须清醒地看到:速度换来的东西,是用信任换来的。

13,000 个 unsafe 调用。缺少人类审核的代码。11 天内生成的 53 万行代码。这些数字背后,是维护者对未来代码质量的隐性承诺——"相信我,它能跑,而且跑得更快"。

对于 Bun 这样由 Anthropic 收购、深度嵌入 Claude Code 的项目来说,这个赌注是值得的——因为 Claude Code 的用户承担不起内存泄漏到 14GB 的代价。

但对于其他开源项目来说,这个模式是否值得复制,需要问自己一个问题:你愿意在生产环境运行一个 AI 写的、人类没审核过的 67 万行 Rust 代码吗?


十、工程师的视角:我们应该学到什么

10.1 AI 是工具,不是银弹

Bun 的案例告诉我们:AI 可以极大加速代码生成和迁移,但架构决策依然需要人类。Phase A/Phase B 的设计是人类的智慧,Zig 到 Rust 的语义映射规则是人类制定的,unsafe 边界的划定也是人类判断的。

AI 负责执行,人类负责设计边界。这是目前 AI coding 工具最有效的工作模式。

10.2 测试套件是 AI 时代的核心竞争力

当 AI 可以生成 53 万行代码时,如何验证这 53 万行代码是正确的?答案是:测试套件

Bun 的 99.8% 测试通过率,是它敢于合并 AI 生成代码的底气。一个好的测试套件,就是 AI 时代的"代码审核"——机器帮你检查每一个边界条件。

这也意味着,投资测试基础设施的回报率比以往任何时候都高

10.3 语言的工具链成熟度比语言本身更重要

Rust 比 Zig 更"难"吗?是的。但 Rust 的工具链(cargo、rustc 的 borrow checker、cargo clippy、miri)提供了 Zig 没有的保障能力。Bun 选择 Rust,不是因为 Rust 语言本身比 Zig 优越,而是因为 Rust 的工具链生态更成熟。

10.4 开源许可证和社区文化开始影响技术选型

Zig 的"禁止 AI 代码"政策,在这次事件后被重新审视。当竞品公司用 AI 重写了整个运行时,Zig 社区的道德洁癖突然变成了竞争劣势。

未来,语言选择不仅要考虑技术特性,还要考虑语言背后社区的开放性和务实性


十一、结论:门已经开了

无论你对这次重写持什么立场,有一点是确定的:这扇门已经被彻底撞开了。

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

速度上天的时代,信任只能自己想办法落地。而这种信任的载体,就是:更严格的测试、更清晰的文档、更成熟的工具链,以及——重新定义什么叫"经过审核的代码"。

Bun 的故事不是一个关于 AI 替代程序员的寓言。它是一个关于:当 AI 把软件开发的成本压缩 100 倍时,游戏规则会发生什么变化的预演。

而我们每一个工程师,都已经是这场游戏规则改变的一部分。


本文数据来源:Bun 官方公告、GitHub Issue #33453、Jarred Sumner X 推文、Create 42 访谈、Bun PORTING.md 文档、CSDN 技术博客等。

推荐文章

js常用通用函数
2024-11-17 05:57:52 +0800 CST
Requests库详细介绍
2024-11-18 05:53:37 +0800 CST
企业官网案例-芊诺网络科技官网
2024-11-18 11:30:20 +0800 CST
gin整合go-assets进行打包模版文件
2024-11-18 09:48:51 +0800 CST
程序员茄子在线接单