编程 Bun 从 Zig 到 Rust 的百万行重写:当 AI 用 9 天改写 JavaScript 运行时的命运

2026-08-06 01:46:57 +0800 CST views 11

Bun 从 Zig 到 Rust 的百万行重写:当 AI 用 9 天改写 JavaScript 运行时的命运

一、引言:一场改写行业认知的技术地震

2026年5月14日,一条 PR #30412 被合并进 oven-sh/bun 的主分支——超过100万行 Rust 代码、6755次提交,几乎全部由 Claude Code 智能体在9天内生成完成。这个 PR 甚至因为体量过大直接把 GitHub 搞崩了,页面无法加载。

Bun 创始人 Jarred Sumner 随后发了一条让整个编程圈炸锅的推文:「我们已经好几个月没有亲手敲代码了。」

这不是一个小项目的实验。Bun 是一个拥有 9.2 万 GitHub Star、每月下载量超过 700 万次的 JavaScript 运行时,是 Claude Code 等 AI 编程工具的底层基础设施。它的「换心手术」,本质上是用 AI 重写了 AI 自己依赖的运行时——这种递归式的自我重构,在软件工程史上几乎没有先例。

但当 PR 合并的喜悦散去,社区的质疑声开始发酵:在99.8%测试通过率的光环下,超过一万个 unsafe 代码块像定时炸弹一样散落在700多个文件中。当 AI 生成代码的速度远远超过人类审查代码的速度时,我们该如何证明这些代码值得信任?

二、背景:Bun 为什么选择 Zig,又为什么放弃

2.1 Zig 的初心

2022年,Jarred Sumner 选择用 Zig 语言构建 Bun,这个决定在当时极具前瞻性。Zig 是一种系统级编程语言,核心理念是「没有隐藏的控制流和内存分配」。对于需要极致性能的 JavaScript 运行时来说,Zig 的几个特性极具吸引力:

  • 无隐藏分配:所有内存操作都显式可见,开发者对每一条内存指令有完全控制
  • 编译时计算:comptime 允许在编译期执行任意代码,减少运行时开销
  • 零依赖哲学:不依赖 libc,不依赖 C++ 标准库,自给自足
  • 与 C 的无缝互操作:可以直接调用 C 函数,无需 FFI 层

Bun 用 Zig 实现了 JavaScript 引擎(JavaScriptCore)、打包器、测试运行器、包管理器等全套工具链,性能确实惊人——启动时间约 3ms,比 Node.js 快一个数量级。

2.2 Zig 的瓶颈

但随着 Bun 规模膨胀到数百万行代码,Zig 的局限性开始暴露:

内存安全依赖开发者自律。Zig 没有 Rust 那样的借用检查器,内存安全完全靠开发者手动保证。在 Bun 这个量级的代码库中,内存 bug 成为团队最大的时间黑洞——不是偶尔出现,而是系统性地消耗调试资源。

生态和工具链薄弱。Zig 社区规模远小于 Rust,IDE 支持、代码分析工具、安全审计工具都严重不足。当你的运行时要服务数百万开发者时,工具链的成熟度直接影响可维护性。

AI 生成代码的兼容性问题。2025年底,Zig 社区对 AI 生成代码的态度趋于保守甚至敌对,多个核心项目明确禁止 AI 代码贡献。这对一个计划用 AI 加速开发的团队来说,是路线层面的冲突。

人才招聘困难。Zig 开发者群体极小,团队扩展受到严重限制。

2.3 Rust 的吸引力

Rust 解决了 Zig 的核心痛点:

  • 编译时内存安全:借用检查器在编译期捕获大部分内存 bug,不需要运行时开销
  • 成熟的生态系统:crates.io 拥有超过 15 万个包,工具链完善,社区活跃
  • AI 友好:Rust 社区对 AI 生成代码持开放态度,且 Rust 的类型系统和编译器错误信息对 LLM 非常友好
  • 性能不妥协:Rust 的零成本抽象和 LLVM 后端保证了与 Zig/C 相当的底层性能

三、重写的架构决策:「忠实移植」还是「重新设计」

3.1 保持不变的两个关键

Jarred 在公告中明确表示,这次重写保持了两个关键不变:

  1. 相同的架构设计——Bun 的整体模块划分、数据流、API 边界都没有改变
  2. 相同的数据结构——底层的数据布局和算法选择基本保持原样

这意味着重写不是「推倒重来」,而是一次「底层实现替换」。Bun 依然不依赖 async Rust,依然使用极少的第三方库,依然保持了零依赖的哲学。

3.2 迁移策略:逐文件翻译

Bun 团队发布的迁移指南要求 Claude Code 尽可能忠实地移植 Zig 代码:

  • 逐文件对应转换
  • 保持相同的函数签名和调用关系
  • 保持相同的数据结构布局
  • 仅在必要时调整语法和类型系统差异

这种策略的优势是风险可控——既然 Zig 版本已经验证了架构的正确性,忠实翻译至少能保证行为一致性。但代价是:依赖手动内存管理的 Zig 代码,「忠实翻译」后不会自动变成内存安全的 Rust 代码。

3.3 unsafe 的代价

这就是问题的核心:在 Zig 中,手动内存管理是显式的、正常的;但翻译成 Rust 后,这些手动管理变成了 unsafe 代码块——它们绕过了 Rust 的借用检查器,打开了通往未定义行为的大门。

根据开发者 dreamreal 的分析,Bun 的 Rust 版本中:

指标Bun (Rust)uv (Rust)差距
unsafe 代码块数量>10,00073~137倍
代码文件数700+
项目体量百万行数十万行~3倍

同为 Rust 生态中的高性能工具,uv 项目的 unsafe 代码量仅为 Bun 的约 1/137。这个差距不是因为 uv 更简单,而是因为 uv 从一开始就是按 Rust 惯用写法设计的,而 Bun 是从 Zig 逐文件翻译过来的。

四、Zig 与 Rust 的语言对比:为什么是 Rust 而不是其他语言

在决定重写语言时,Bun 团队评估了多个候选方案。除了 Rust,Go、C++、甚至 TypeScript 都在讨论范围内。最终选择 Rust 并非偶然,而是在多个维度上做了权衡。

4.1 为什么不是 Go

Go 是一个成熟、高效、生态丰富的语言,但它有几个不适合 Bun 场景的特性:

  • GC 延迟:Go 的垃圾回收器在高吞吐量场景下可能产生微秒级的停顿,对 JavaScript 运行时的延迟敏感型工作负载不可接受
  • runtime 开销:Go 的 runtime 本身就有数 MB 的内存占用,对于追求极致性能的 Bun 来说是额外负担
  • 缺少 unsafe:Go 没有 unsafe 机制,在需要直接操作内存的底层场景(如 JS 引擎的垃圾回收器、SIMD 优化)会受限

4.2 为什么不是 C++

C++ 有几十年的性能优化历史,但它的问题在于:

  • 内存安全无保障:C++ 同样没有编译时内存安全检查,选择 C++ 只是从一种手动内存管理语言换到另一种
  • ABI 稳定性问题:C++ 的 ABI 不稳定,不同编译器、不同版本之间的二进制兼容性是噩梦
  • 编译速度慢:大型 C++ 项目的编译时间以小时计,严重影响开发迭代

4.3 Rust 的独特优势

Rust 在这次选择中脱颖而出,有几个独特优势:

所有权系统 + 借用检查器。这是 Rust 的核心创新——在编译期通过类型系统和生命周期标注保证内存安全,同时不引入运行时开销。对于 Bun 这种每天处理数十亿次请求的运行时来说,这种保证的价值难以估量。

零成本抽象。Rust 的抽象层不会产生额外的运行时成本。泛型通过单态化实现,trait 通过虚表或内联实现,迭代器通过零开销抽象链实现。这保证了用安全的高层 API 写出的代码,性能与 unsafe 手写代码相当。

LLVM 后端。Rust 使用 LLVM 作为编译后端,继承了 LLVM 几十年的优化能力。这意味着 Rust 代码可以享受到与 C/C++ 同等的优化水平——向量化、内联、循环展开、常量折叠等优化自动生效。

包管理生态。crates.io 拥有超过 15 万个包,虽然 Bun 有意减少第三方依赖,但 Rust 生态的成熟度意味着:当需要特定功能时(如压缩、加密、序列化),有经过充分测试的高质量实现可供选择。

AI 友好性。这一点在 Bun 的案例中被证明至关重要。Rust 编译器的错误信息极其详细——它不仅告诉你哪里错了,还告诉你为什么错、怎么改。这种结构化的反馈对 LLM 来说非常友好,大幅提高了 AI 生成代码的首次通过率。相比之下,Zig 的错误信息相对简洁,对 AI 不够友好。

五、99.8% 测试通过率的真相

4.1 测试在测量什么

Bun 团队宣称新版本通过了现有测试套件 99.8% 的测试。这个数字本身没有问题——它准确地反映了新实现与旧实现之间行为一致性

但需要理解的是:测试套件测量的是「外部可观察行为」,而不是「内部内存安全」。一个依赖手动内存管理的实现和一个使用 Rust 安全抽象的实现,可以通过完全相同的测试——只要它们对外表现一致。

4.2 未通过的 0.2%

0.2% 未通过的测试大多是边缘场景和平台特定行为。这些不通过并不意味着重写失败,但它们标记了需要人工审查的区域。

4.3 测试无法验证的维度

更关键的问题是:哪些维度的正确性是测试套件根本无法验证的?

  • unsafe 代码块的正确性:每个 unsafe 块是否真的满足了 Rust 的安全契约?
  • 未定义行为:是否存在微妙的 UB(未定义行为),它们可能在特定条件下导致数据损坏或崩溃?
  • 并发安全:unsafe 代码中的原子操作和内存序是否正确?
  • 跨平台一致性:在不同 libc 实现、不同编译器版本下,unsafe 代码的行为是否一致?

Amazon 曾联合 Rust 基金会发起专门的社区项目,验证 Rust 标准库中的 unsafe 代码。标准库的 unsafe 代码规模小、审查严格且由人工编写,即便如此仍然出现过二十多个可追溯到 unsafe 代码的 CVE 漏洞。

对于 Bun 这样超过一万个 unsafe 代码块的项目,验证工作量是天文数字。

六、代码实战:unsafe 迁移的典型模式

5.1 Zig 内存管理模式

在 Zig 中,手动内存管理是标准实践:

// Zig: 显式分配和释放
const allocator = std.heap.page_allocator;
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);

// 自定义结构体的手动内存管理
const List = struct {
    items: []Item,
    len: usize,
    capacity: usize,

    fn init(alloc: Allocator) !List {
        return List{
            .items = try alloc.alloc(Item, 8),
            .len = 0,
            .capacity = 8,
        };
    }

    fn deinit(self: *List, alloc: Allocator) void {
        alloc.free(self.items);
    }
};

5.2 翻译为 Rust 的 unsafe 模式

同样的逻辑翻译成 Rust 后:

// Rust: unsafe 翻译版——绕过借用检查器
use std::alloc::{alloc, dealloc, Layout};

struct List {
    items: *mut Item,
    len: usize,
    capacity: usize,
}

impl List {
    unsafe fn init() -> List {
        let layout = Layout::array::<Item>(8).unwrap();
        let items = alloc(layout) as *mut Item;
        List {
            items,
            len: 0,
            capacity: 8,
        }
    }

    unsafe fn deinit(&mut self) {
        let layout = Layout::array::<Item>(self.capacity).unwrap();
        dealloc(self.items as *mut u8, layout);
    }
}

这段代码在 Rust 中是完全合法的,但它的安全性完全依赖开发者手动保证——和 Zig 版本没有本质区别。

5.3 惯用的 Rust 安全写法

如果用 Rust 的安全抽象重写:

// Rust: 惯用写法——编译时内存安全
struct List {
    items: Vec<Item>,
}

impl List {
    fn new() -> List {
        List {
            items: Vec::with_capacity(8),
        }
    }

    // deinit 是自动的——Vec 在 drop 时自动释放内存
    // 不需要手动管理生命周期
    // 借用检查器保证不会有 use-after-free 或 double-free
}

惯用写法的 List 不需要 unsafe,不需要手动 dealloc,不需要 Layout,不需要指针转换。借用检查器在编译期就保证了内存安全。

但 Bun 的迁移策略选择了前者——因为「忠实翻译」比「重新设计」更可控、更快速、风险更低。

5.4 性能对比:两种写法的差异

你可能会问:惯用写法的性能会差多少?实际上,在大多数场景下,Vec 的性能与手动管理几乎无异——甚至可能更好,因为编译器能对安全代码做更激进的优化。

// 性能测试:unsafe 手动管理 vs Vec
use std::alloc::{alloc, dealloc, Layout};
use std::time::Instant;

const N: usize = 1_000_000;

fn bench_unsafe() -> u64 {
    let start = Instant::now();
    unsafe {
        let layout = Layout::array::<u64>(N).unwrap();
        let ptr = alloc(layout) as *mut u64;
        for i in 0..N {
            ptr.add(i).write(i as u64);
        }
        let mut sum = 0u64;
        for i in 0..N {
            sum = sum.wrapping_add(ptr.add(i).read());
        }
        dealloc(ptr as *mut u8, layout);
        sum
    }
    start.elapsed().as_nanos() as u64
}

fn bench_vec() -> u64 {
    let start = Instant::now();
    let mut vec: Vec<u64> = Vec::with_capacity(N);
    for i in 0..N {
        vec.push(i as u64);
    }
    let mut sum = 0u64;
    for &v in &vec {
        sum = sum.wrapping_add(v);
    }
    sum
    // vec 在这里自动 drop,释放内存
    start.elapsed().as_nanos() as u64
}

fn main() {
    println!("unsafe: {}ns", bench_unsafe());
    println!("vec:    {}ns", bench_vec());
}

在 Release 模式下,两者的性能差距通常在 5% 以内。而在 debug 模式下,unsafe 版本的优势更明显,但生产环境几乎不会用 debug 构建。

七、性能基准测试结果

6.1 二进制体积

Rust 版本的二进制文件体积比 Zig 版本缩小了 3-8 MB。在 Linux x64 平台上,原本约 93MB 的二进制文件有所缩减。体积缩减主要来自 Rust 标准库的链接优化和 Zig 编译器的一些冗余输出。

6.2 启动时间和吞吐量

从官方公布的基准测试来看:

指标Zig 版本Rust 版本变化
启动时间~3ms~3ms持平
HTTP 吞吐量基准线+2-5%略有提升
内存占用基准线-3-5%略有下降

性能基本持平甚至略有提升,这对于一次百万行级别的重写来说已经是极好的结果。Jarred 强调,迁移的动机不是性能,而是安全性——但性能没有退步,说明 Rust 的零成本抽象确实兑现了承诺。

6.3 内存泄漏修复

更重要的是,Rust 重写修复了多个长期存在的内存泄漏和 flaky 测试问题。这些 bug 在 Zig 版本中存在了数年,消耗了团队大量调试时间,但始终难以根治。Rust 的所有权系统虽然没有完全消除 unsafe,但在那些使用安全抽象的代码区域,确实提供了编译时的内存安全保证。

八、行业影响:AI 重写基础设施的范式转移

7.1 AI 编程的新阶段

Bun 的重写标志着 AI 编程从「辅助」阶段进入「接管」阶段。过去,AI 生成代码是人类开发者的辅助工具——帮你写个函数、补全几行代码、生成测试用例。但 Bun 的案例中,AI(Claude Code)完成了:

  • 100万行代码的完整重写
  • 6755次 git commit
  • 从 Zig 到 Rust 的跨语言翻译
  • 99.8%的测试通过率

这不是辅助,这是工程执行。

7.2 审查速度的悖论

但这里存在一个根本性矛盾:AI 生成代码的速度远远超过人类审查代码的速度。

按照代码生成的速度阅读这些代码,不是人类能够做到的事情。Bun 团队目前的信心主要来自测试套件——但如前所述,测试套件从未验证过这次迁移最核心的目标(内存安全)。

有人认为这是早期阶段,后续会有更多 PR 逐步清理 unsafe 代码。但验证一段 unsafe Rust 代码是否真正安全是极其困难的事情——当前学术界最先进的方法是半自动化分析工具和需要人工编写形式化规范的实验性验证器,不存在能让 unsafe 代码变得安全的自动化工具。

7.3 Zig 社区的反弹

这次迁移也在 Zig 社区引发了强烈反弹。多个核心项目对 AI 生成代码持保守甚至敌对态度,而 Bun 作为 Zig 生态的旗舰项目选择离开,被视为对 Zig 信心的重大打击。

但从工程角度看,这是务实的选择。当你的运行时要服务数百万开发者时,生态成熟度、人才储备、工具链支持——这些「软因素」的重要性不亚于语言本身的技术优势。

九、风险评估:Bun 的安全隐患在哪

8.1 unsafe 不等于不安全

需要强调的是:unsafe 代码不是「有 bug 的代码」。它只是意味着编译器不再替你保证安全性——正确性由开发者负责。很多高性能 Rust 项目(如 tokio、serde)都大量使用 unsafe,但它们是经过精心审计和测试的。

Bun 的问题不是「有 unsafe」,而是「有太多 unsafe,且大部分由 AI 在9天内生成,人类审查的速度远跟不上」。

8.2 潜在风险场景

未定义行为不会通过测试失败主动暴露,它可能以其他方式出现:

  • 18个月后在某个特定的 libc 实现上被发现
  • 某家路由器厂商选用某个行为古怪的 musl 版本后问题浮出水面
  • 在特定的并发场景下触发数据竞争
  • 在内存压力测试下暴露 use-after-free

这些场景不会让 Bun 立即崩溃,但可能在特定条件下导致数据损坏或安全漏洞。

8.3 安全审计的挑战

对超过一万个 unsafe 代码块进行完整安全审计,工作量相当于一个中型团队全职工作数年。这不是「后续清理」几个 PR 就能完成的事情,而是一场持续数年的审计工程。

十、开发者视角:我们应该怎么看待这件事

9.1 Bun 仍然是好的选择

尽管存在 unsafe 争议,Bun 仍然是 JavaScript 运行时的最佳选择之一。它的性能优势、开发体验、工具链完整性都是实实在在的。Rust 重写至少在两个维度上让 Bun 变得更好:

  1. 生态优势:Rust 社区的规模和成熟度远超 Zig,未来维护成本会降低
  2. 部分内存安全:虽然 unsafe 代码很多,但安全抽象的代码区域确实获得了编译时保证

9.2 关注 canary 版本的稳定性

如果你在生产环境使用 Bun,建议:

  • 暂时使用 stable 版本,等待 Rust 重写充分稳定
  • 关注 canary 版本的测试通过率变化
  • 在升级前进行充分的回归测试

9.3 对 AI 编程的理性认知

Bun 的案例给我们的启示不是「AI 不行」或「Rust 不行」,而是:

  • AI 擅长模式匹配和翻译,但在创造性设计和安全审计方面仍需人类
  • 测试通过率不等于安全性,行为一致性和内存安全性是两个不同的维度
  • AI 生成代码的审查需要新方法论,传统的逐行代码审查在百万行级别已不可行

十一、总结与展望

Bun 从 Zig 到 Rust 的百万行重写,是软件工程史上的一个里程碑事件。它验证了几个重要命题:

  1. AI 可以完成大规模代码翻译——100万行、6755次提交、9天完成,这个速度是人类团队的数十倍
  2. Rust 的零成本抽象是真实的——百万行级别的重写没有性能退步
  3. 编译时安全有局限——当迁移策略选择「忠实翻译」而非「重新设计」时,unsafe 代码会成倍增加
  4. AI 生成代码的验证是未解难题——测试套件无法验证内存安全,安全审计的工作量远超预期

对于整个行业来说,Bun 的案例敲响了警钟:当 AI 可以在几天内生成百万行代码时,我们的代码审查流程、安全审计方法、质量保证体系都需要重新设计。

Bun 的 Rust 版本目前仅通过 canary 渠道发布,Jarred 坦诚地表示优化工作仍在进行中,最终版本发布前还会有清理工作。这是一个成熟开源项目应有的节奏——不为了抢首发而牺牲质量。

未来几年,围绕 Bun 这百万行 unsafe 代码的安全审计,将成为 Rust 社区最重要的安全课题之一。而 Bun 团队如何平衡「快速迭代」和「安全验证」,也将为整个 AI 编程领域提供宝贵的经验教训。

不管怎样,Bun 已经走上了 Rust 这条路。对于 JavaScript 开发者来说,这意味着更好的生态支持和长期维护;对于 Rust 社区来说,这意味着一个百万行级别的真实世界 unsafe 代码审计案例;对于 AI 编程来说,这意味着从「辅助」到「接管」的转折点已经到来。

而我们每个人——无论是代码的编写者还是审查者——都需要重新思考:在 AI 时代,什么才是「值得信任的代码」。

推荐文章

平面设计常用尺寸
2024-11-19 02:20:22 +0800 CST
页面不存在404
2024-11-19 02:13:01 +0800 CST
Vue3结合Driver.js实现新手指引功能
2024-11-19 08:46:50 +0800 CST
Go配置镜像源代理
2024-11-19 09:10:35 +0800 CST
程序员茄子在线接单