编程 Bun 从 Zig 到 Rust:Claude 亲手重写 96 万行代码,AI 编程的「成人礼」

2026-05-18 14:44:35 +0800 CST views 16

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 的所有权系统和借用检查器在编译期就能捕获内存泄漏——Rc vs Arc 的选择、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.ArenaAllocatorstd.heap.GeneralPurposeAllocator → Rust 的 mimalloc-rsjemallocatorstd::alloc::System
  • 字节操作:Zig 的 std.memstd.fmt → Rust 的 std::memstd::fmtregexbytes
  • 文件 I/O:Zig 的 std.fs → Rust 的 std::fstokio::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(接口),allocfreeresize 三个方法。Rust 的标准分配器也是 trait(Allocator trait in std::alloc),但对于大多数场景,使用 BoxVecString 等智能指针类型就已经足够了——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% 的失败用例主要涉及两个边界情况:

  1. 某些 Zig 特有的未定义行为(UB)在 Rust 中无法直接等价实现
  2. 浮点数精度在某些极端情况下有微小差异

这些差异被标记为「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_simdfaster)实现,但迁移这个层面需要大量的手动优化工作。

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
}

对于 StringVecBox 等拥有所有权的类型,Rust 会在编译期强制追踪所有权的转移。一旦一个值的所有者离开了作用域,Rust 自动调用 drop 释放内存——你无法忘记释放内存,因为编译器的借用检查器(Borrow Checker)会阻止这种代码通过编译。

这就是 Sumner 选择 Rust 的核心理由:不是 Rust 性能更好(Zig 和 Rust 在 Bun 的场景下性能差距很小),而是 Rust 的类型系统能够在编译期保证内存安全,而不是依赖程序员的纪律。

3.2 Rust 的工具链优势

除了内存安全,Rust 的生态也是选择它的重要原因:

维度ZigRust
async/awaitZig 的 async 是实验性功能,文档不完善tokio/async-std 生态成熟稳定
FFI良好,但与 C++ 交互需要手写 wrapperautocxx/cxx 库自动生成安全 bindings
调试工具gdb/lldb,但 Zig 特定支持有限rust-gdb/rust-lldb + miri 工具链
静态分析基本没有clippymirisanitizer 生态完善
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 选择重写的条件可以总结为三点:

  1. 问题分散且深层:内存泄漏问题分布在多个模块,不是修一个函数能解决的
  2. 新语言有编译期保证:Rust 的所有权系统能够「编译期防止同类错误再次出现」
  3. 有 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 的经验表明,以下几个维度需要重点关注:

  1. 启动时间:Rust 的静态链接使二进制文件更大,可能影响冷启动时间
  2. SIMD 路径:需要为 Rust 版本单独优化 SIMD 代码
  3. 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 字

推荐文章

2024年微信小程序开发价格概览
2024-11-19 06:40:52 +0800 CST
nginx反向代理
2024-11-18 20:44:14 +0800 CST
Vue3中的自定义指令有哪些变化?
2024-11-18 07:48:06 +0800 CST
mysql删除重复数据
2024-11-19 03:19:52 +0800 CST
介绍Vue3的静态提升是什么?
2024-11-18 10:25:10 +0800 CST
JavaScript 实现访问本地文件夹
2024-11-18 23:12:47 +0800 CST
MySQL 优化利剑 EXPLAIN
2024-11-19 00:43:21 +0800 CST
MySQL数据库的36条军规
2024-11-18 16:46:25 +0800 CST
程序员茄子在线接单