编程 Bun 从 Zig 到 Rust:96万行代码、6天、AI 重写——一场教科书级的技术债务迁移战

2026-07-30 08:43:40 +0800 CST views 14

Bun 从 Zig 到 Rust:96万行代码、6天、AI 重写——一场教科书级的技术债务迁移战

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

2026年5月11日,Bun创始人 Jarred Sumner 在 X 上发了一条推文:

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

就这么一句。四年前,Bun 因为选择了 Zig 而显得特立独行——在 Node.js 用 JavaScript/ C++、Deno 用 Rust 的格局下,Bun 选择 Zig 被视为一股清流。四年后,同一个创始人用一条推文宣告了 Zig 版本的死刑。

这不是一次普通的版本迭代,而是一次震撼整个开源社区的技术迁移:

  • 时间:约6天
  • 代码量:约96万行
  • 方式:Claude AI 主导重写
  • 测试通过率:99.8%(Linux x64 glibc 环境)
  • 触发原因:Bun 的内存泄漏导致 Claude Code(深度依赖 Bun)进程在3小时内从1.7GB飙升到14GB

本文将从架构、动机、技术细节、性能数据、社区争议等多个维度,深度拆解这次可能是2026年最具戏剧性的开源技术迁移事件。


一、背景:Bun 是什么?为什么要用 Zig 重写 JavaScript 运行时?

1.1 Bun 的技术定位

Bun 是一个"all-in-one"的 JavaScript 运行时,集成了:

组件Bun 的方案对比 Node.js
JavaScript 引擎JavaScriptCore (Safari)V8 (Chrome)
包管理器内置(npm 兼容)npm
构建/打包器内置webpack/vite
测试框架内置jest/vitest
核心语言ZigC++

Bun 的核心卖点是速度——启动时间约3ms,而 Python 约45ms。Claude Code 负责人 Boris Cherney 曾公开表示,Bun 几乎毫无悬念地击败了所有其他方案,成为 Claude Code 的运行时选择。

1.2 Zig 为什么曾是正确答案

Zig 的设计哲学是"没有隐藏控制流、没有隐藏内存分配、没有预处理器"。对于一个需要精细控制内存、与 C/C++ 库深度互操作的 JavaScript 运行时来说,Zig 看起来是完美的选择:

// Zig 的内存控制示例:显式 alloc + 释放
const allocator = std.heap.page_allocator;
const memory = try allocator.alloc(u8, 1024);
defer allocator.free(memory);

没有 GC、没有隐藏的析构器、没有运行时——所有内存操作都是显式的。这正是 Bun 需要的:JavaScript 本身是 GC 语言,而运行时底层需要手动管理内存,两者必须精确配合。

1.3 问题的种子

然而,Zig + JavaScript 运行时 = 两层内存模型叠加

  • JavaScript 层:GC 管理对象生命周期
  • Zig 层:手动管理底层内存(缓冲区、系统调用、FFI)

这两层之间的边界极其脆弱。当 JavaScript 回调触发 Zig 层的内存操作时,如果生命周期处理不当,就会产生 use-after-free。更糟糕的是:Zig 的编译器无法捕获这些跨语言边界的内存安全问题——它们只能靠运行时崩溃来暴露。


二、问题的爆发:Zig 版 Bun 如何拖垮了 Claude Code

2.1 Claude Code 的内存泄漏事件

2026年3月12日,GitHub Issue #33453 被提交到 Claude Code 仓库:

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

另一份 Issue 记录更夸张:运行14小时后,Claude Code 进程占用23GB 虚拟内存,143.8% CPU,系统完全卡死。

2.2 Bun 自身的问题清单

内存泄漏只是冰山一角。Zig 版 Bun 的问题可以归为几类:

1. 跨语言边界内存安全问题

// node:zlib 的 use-after-free 崩溃
// node:http2 的 re-entrant JS 回调导致 hashmap 失效
// UDPSocket.sendMany() 的越界写入
// fs.watch() 的 GC 根引用计数下溢导致内存泄漏

2. 4700 个 Open Issues

Node.js 驱动着几乎整个互联网的服务器,目前约1700个 open issues;而用户规模远小于 Node.js 的 Bun,却积累了约4700个 open issues。这意味着工程债务的积累速度远超消化速度。

3. 路线图失衡

Reddit 用户 Xtergo 的评价很有代表性:

"任何新运行时都会有成熟度问题,这些问题最终会随着时间慢慢被修复。但 Bun 的路线图看起来更像是在不断叠加新功能,而不是优先解决稳定性和 Bug 修复问题。"

2.3 哲学决裂:Zig 社区 vs AI 时代

更深层的问题是文化冲突。Bun 团队曾 fork Zig 并做了大量优化——通过引入 LLVM 并行代码生成,debug 编译速度提升了四倍。但这些优化始终无法 upstream 回 Zig 官方,原因是 Zig 社区严格的"no-AI policy"——禁止 AI 生成 issue、PR 甚至评论。

Zig 基金会成员 Loris Cro 公开表示,大量 LLM 贡献只会制造"幻觉 PR""垃圾噪音"以及动辄上万行、根本无法维护的提交。

讽刺的是:Zig 社区全面封禁 AI 生成代码,而 Bun 团队现在正准备用 Claude AI 把整个 Zig 实现迁移出去。


三、Rust 重写:6天、96万行代码是如何炼成的

3.1 触发事件:Anthropic 收购 Bun

2025年12月,Anthropic 收购了 Bun,官方说法是"加速 Claude Code 能力"。本质是让 Bun 成为 Claude Code 背后的运行时、包管理器、bundler 和测试工具。

这直接导致了两个后果:

  1. Claude Code 与 Bun 的耦合从"合作"变成了"依赖"——内存泄漏直接拖垮 Claude Code
  2. Anthropic 拥有 Claude AI 的使用权,让"AI 重写 Bun"成为了可能

3.2 PORTING.md:576行的 AI 迁移指南

重写开始前,Jarred Sumner 创建了一份极其详细的迁移文档(PORTING.md),长达576行,将迁移分为两个阶段:

Phase A:Claude 逐文件忠实保留 Zig 的逻辑,即便 Rust 代码暂时不能编译也没关系。目的是先完成语义翻译,再处理编译问题。

Phase B:逐个 crate 解决编译、构建和运行问题。

文档甚至细到规定:

  • 文件命名规范
  • crate 引用结构
  • 禁止使用 tokio/rayon/hyper/futures
  • 禁止使用 async fn
  • 所有 unsafe 必须写明 SAFETY 注释
  • 遇到不确定逻辑时,宁可留下 TODO,也不要让 AI 自行猜测

这种"先忠实翻译,再逐步优化"的策略,是保证6天完成96万行代码迁移的关键。

3.3 时间线:从"根本跑不起来"到"99.8%通过测试"

Day 1-3(5月初)

  • 分支 claude/phase-a-port 创建
  • 数十万行 AI 生成的 Rust 代码与原始 Zig 实现并排存在
  • 约4000次 commit,96万行代码

Day 4(5月7日)

  • Jarred 发推:只剩3个编译错误
  • bun runpackage.json scripts 已能运行
  • 意味着 JSON parser、AST、logger、module resolver、文件系统遍历等基础能力全部迁移完成
  • "JavaScript runtime runs JavaScript" —— Rust 版的 Bun 已经能执行 JS 代码了

Day 5(5月9日)

  • Rust 版本在 Linux x64 glibc 环境通过 Bun 测试套件的 99.8%
  • Jarred 透露心声:"我真的很厌倦为内存泄漏、崩溃和稳定性问题而担忧。"

Day 6(5月11日)

  • Jarred 发推宣告 Zig 版本终结
  • 社区爆炸

3.4 关键架构决策

1. 不使用 async/await 和 tokio

Bun 的事件循环(event loop)是其核心。Rust 生态中 tokio 是最成熟的异步运行时,但 Bun 选择了一条不同的路:

// Bun 的事件循环采用了类似 Zig 的底层设计
// 使用 tagged pointer 处理 event loop task
// 而不是 tokio 的 Task 系统

struct Task {
    pointer: *mut (),           // 原始指针
    vtable: *const TaskVTable,   // 虚表
    // 避免了 Rust 原生 async/await 的状态机开销
}

原因:tokio 的 Task 系统会带来不可忽视的调度开销,而 Bun 需要的是极致低延迟。使用固定大小的 tagged union + 手动调度,反而比通用异步运行时更高效。

2. FFI 边界清理

Bun 内部有大量 C/C++ 代码(JavaScriptCore、libuv 等),Rust 版本的 unsafe 主要集中在这些 FFI 边界:

// FFI 边界示例:从 Bun 原始 Zig 代码迁移
// 这里是典型的 unsafe FFI 调用,必须注明 SAFETY
#[unsafe(no_mangle)]
extern "C" fn bun__http2__server_new(
    server: *mut Http2Server,
    options: *const Http2Options,
) -> c_int {
    // SAFETY: caller guarantees server and options are valid
    // and not aliased during this call
    unsafe {
        let srv = &mut *server;
        let opts = &*options;
        srv.init(opts)
    }
}

四、争议:13,000 个 unsafe vs 73 个 unsafe

4.1 数字之争

重写完成后,t3.gg 创始人 Theo 在 X 上发布了一组对比数据:

"uv 包含35万行 Rust 代码,以及73个 unsafe 调用。Bun Rust 移植版已经有68.1万行 Rust 代码,并且有超过 13,000个 unsafe 调用。"

73 vs 13,000,差了约180倍。

Jarred 的回应几乎立刻:

"今天已经下降了大约2000。我预计它会稳定在1万左右,因为 Bun 的大部分内容都是用 C 和 C++ 编写的,这种情况不会改变。"

这个解释在技术上有一定道理。Bun 需要与大量底层 C/C++ 代码深度互操作:JavaScriptCore 引擎、文件系统、网络 API,这些都绕不开 unsafe。而 uv 是一个相对"纯 Rust"的项目。

4.2 流程之辩

开发者社区的批评更集中在工程流程上:

"UV rust 是由真正的开发人员编写的,每一行代码都经过了审查。Bun rust 由 Agents 编写,由 Agents 审核,并由 Agents 批准和合并。"

这个批评点出了 AI 重写软件的一个核心问题:谁来保证代码质量?

传统的代码审查(Code Review)依赖有经验的工程师发现逻辑错误、性能问题、安全隐患。当 AI 生成、AI 审核、AI 合并时,这个链条上的人是缺位的。

4.3 实际风险评估

让我们理性分析 unsafe 数量的实际风险:

高风险 unsafe(需要仔细审查):

  • 内存操作:ptr::read(), ptr::write(), Vec::from_raw_parts()
  • 线程操作:unsafe impl Send/Sync
  • FFI 调用:外部 C 库函数

低风险 unsafe(模板化、相对安全):

  • FFI 绑定声明:extern "C" { fn ... }
  • 内联汇编
  • 底层硬件操作

Bun 的13,000个 unsafe 中,相当一部分是 FFI 绑定声明,这些实际上是 Rust 的最佳实践——正确声明外部 C 函数接口。真正需要深度审查的 unsafe 应该只占少数。


五、性能对比:Rust 版 Bun 的真实提升

5.1 Claude Code 的实际收益

2026年7月19日,开发者 Simon Willison 在其个人博客指出:2026年6月17日发布的 Claude Code v2.1.181 版已整合 Rust 重构版 Bun,在 Linux 平台上启动速度快10%

这验证了 Rust 迁移的一个核心假设:更少的内存问题 → 更稳定的性能表现。

5.2 启动时间对比

运行时Linux 启动时间说明
Python~45ms基准
Node.js~25msV8 优化
Bun (Zig, 旧版)~3ms极快
Bun (Rust, 新版)~2.7ms快了10%

5.3 内存稳定性

这是最关键的改进。Rust 的 borrow checker 在编译期捕获 use-after-free、data race 等内存安全问题:

// Rust 的编译期内存安全保证示例
fn process_data(data: &[u8]) -> Vec<u8> {
    // 编译期保证:
    // 1. data 生命周期内不会被修改
    // 2. 返回值的数据来自 data,不会出现 dangling pointer
    // 3. 多线程访问 data 时,编译器强制同步
    data.iter().map(|&b| b.wrapping_add(1)).collect()
}

相比之下,Zig 依赖程序员的"极度谨慎"和事后 fuzzing/ASAN 来发现内存问题——在96万行规模下,这是不可持续的。


六、架构设计:Rust 版 Bun 的核心模块拆解

6.1 模块分层

┌─────────────────────────────────────────┐
│           JavaScript Application          │
├─────────────────────────────────────────┤
│        JavaScriptCore Engine (C++)        │
├─────────────────────────────────────────┤
│      Bun Runtime Core (Rust)             │
│  ┌──────────┬──────────┬──────────┐    │
│  │ Event    │ Module    │  File    │    │
│  │ Loop     │ Resolver  │  System  │    │
│  ├──────────┴──────────┴──────────┤    │
│  │        Network / HTTP/2         │    │
│  ├───────────────────────────────┤    │
│  │      Zlib / Compression       │    │
│  ├───────────────────────────────┤    │
│  │      SQLite / Database        │    │
│  └───────────────────────────────┘    │
├─────────────────────────────────────────┤
│         System Call Layer (libc)          │
└─────────────────────────────────────────┘

6.2 事件循环的 Rust 实现

这是最关键的部分。Bun 的事件循环需要处理:

  • Timer (setTimeout/setInterval)
  • I/O (文件系统、网络)
  • 微任务队列 (Promise)
  • 宏任务队列 (setTimeout callback)
  • 进程退出回调
  • 非阻塞文件 I/O

原始 Zig 版本使用 tagged union + 手动调度:

// Zig 版本(简化)
const Task = union(enum) {
    timer: struct { id: u64, expiration: u64 },
    io: struct { fd: os.fd_t, op: IoOperation },
    js_callback: struct { callback: *JSCallback, this: *JSValue },
    promise: struct { resolve: *JSValue, reject: *JSValue },
};

Rust 版本的等价设计:

// Rust 版本(简化)
enum TaskKind {
    Timer { id: u64, expiration: u64 },
    Io { fd: RawFd, op: IoOperation },
    JsCallback { callback: NonNull<JSFunction>, this: NonNull<JSValue> },
    Promise { resolve: NonNull<JSValue>, reject: NonNull<JSValue> },
}

struct Task {
    kind: TaskKind,
    state: AtomicU8,    // 状态机
    next: Option<NonNull<Task>>,
    // 使用 NonNull 保证非空性
}

impl Task {
    // Rust 保证:所有字段访问都是安全的
    // borrow checker 确保生命周期正确
}

6.3 HTTP/2 服务端实现

HTTP/2 的复杂性在于多路复用和流控制。Rust 版本需要正确处理:

// HTTP/2 流处理的核心结构
pub struct Http2Stream {
    id: u32,                        // 流 ID
    state: Http2StreamState,
    send_window: i32,               // 发送窗口
    recv_window: i32,               // 接收窗口
    headers: HeaderMap,
    data: BytesMut,                 // 缓冲区
    // 关键:所有 mutable 访问通过 Mutex 保护
    // 避免 data race
}

pub struct Http2Server {
    settings: Http2Settings,
    streams: HashMap<u32, Arc<Mutex<Http2Stream>>>,
    connection: TcpStream,
    // 使用 Arc<Mutex<T>> 而不是 Arc<T>
    // 确保流级别的并发安全
}

impl Http2Server {
    pub fn handle_frame(&self, frame: Http2Frame) -> Result<()> {
        match frame {
            Http2Frame::Headers(h) => self.on_headers(h),
            Http2Frame::Data(d) => self.on_data(d),
            Http2Frame::Ping(p) => self.on_ping(p),
            _ => Ok(()),
        }
    }
}

6.4 模块解析器的 Rust 迁移

Node.js/Bun 的模块解析是出了名的复杂——需要处理:

  • CommonJS (require)
  • ES Modules (import/export)
  • 路径解析(相对路径、绝对路径、node_modules)
  • 条件导出(exports.conditions
  • 裸说明符(@scope/package
  • 各类配置文件(package.json exports 字段等)
// 模块解析器核心
pub struct ModuleResolver {
    fs: FileSystem,
    conditions: ExportConditions,  // 导出条件
    cache: LruCache<PathBuf, Resolution>,
    // LRU 缓存避免重复解析
}

impl ModuleResolver {
    pub fn resolve(
        &self,
        specifier: &str,
        from: &Path,
    ) -> Result<Resolution> {
        // 1. 检查缓存
        let cache_key = self.make_cache_key(specifier, from);
        if let Some(cached) = self.cache.get(&cache_key) {
            return Ok(cached.clone());
        }

        // 2. 判断模块类型
        let specifier_type = self.classify(specifier)?;

        // 3. 路径解析
        let path = match specifier_type {
            SpecifierType::Relative => self.resolve_relative(specifier, from)?,
            SpecifierType::Absolute => self.resolve_absolute(specifier)?,
            SpecifierType::Bare => self.resolve_bare(specifier, from)?,
            SpecifierType::Builtin => return Ok(Resolution::builtin(specifier)),
        };

        // 4. 缓存并返回
        let resolution = Resolution { path, specifier_type };
        self.cache.put(cache_key, resolution.clone());
        Ok(resolution)
    }
}

七、工程启示:这次迁移教会我们什么

7.1 语言选择的动态性

Bun 的故事告诉我们:语言选择不是一劳永逸的决策。

阶段选择理由结果
2020-2022Zig极致控制、无隐藏内存分配、与 C 互操作性能优秀,但规模扩大后稳定性问题累积
2026Rust编译期内存安全、工具链成熟、社区活跃更稳定的运行时,但 unsafe 数量惊人

关键洞察:语言选择的正确性随项目规模、团队能力和组织上下文而变化。 四年前的"正确答案"在四年后可能成为障碍。

7.2 AI 辅助重写的工程模式

Bun 的迁移揭示了一种新的"AI 重写工程模式":

1. 人工制定迁移规范(PORTING.md)
   ↓
2. AI 执行语义翻译(Phase A:忠实翻译,暂不考虑编译)
   ↓
3. 人工验收 + AI 修复(Phase B:逐个 crate 解决编译问题)
   ↓
4. 完整测试套件验证(99.8% 通过率)
   ↓
5. 人工最终审查 + 合并决策

这种"规范 + AI 翻译 + 人工验收"的模式,可能是未来大规模代码迁移的标准流程。纯 AI 自动合并在当前阶段风险仍然过高。

7.3 unsafe 的管理策略

Rust 不是银弹。Bun 的13,000个 unsafe 提醒我们:Rust 的安全性保证止步于 FFI 边界

管理策略:

  1. 明确标注:每个 unsafe 都要有 SAFETY 注释,说明前置条件
  2. 范围隔离:将 unsafe 代码集中到独立的模块,用 safe wrapper 包裹
  3. 测试保护:unsafe 函数需要额外的测试覆盖
  4. 渐进清理:像 Bun 那样,合并后持续减少 unsafe 数量

7.4 测试的战略价值

99.8%的测试通过率是这次迁移成功的关键保障。它证明了:

  • 有了充分覆盖的测试套件,大规模重写是可行的
  • 测试是迁移的"护栏",而非阻碍
  • AI 生成的代码只要通过了测试集,就能保证行为一致性

八、展望:Rust 版 Bun 的未来

8.1 当前状态

  • Linux x64 glibc:99.8% 测试通过,生产可用
  • macOS / Linux ARM64:仍在优化中
  • Windows:尚未开始移植

8.2 即将到来的改进

根据 Jarred 的规划:

  1. unsafe 数量减少:目标是接近 uv 的比例(相对于代码量)
  2. 性能进一步优化:Rust 的零成本抽象允许更激进的优化
  3. 跨平台一致性:解决 macOS/Windows 的兼容性问题
  4. 稳定性优先于新功能:路线图调整,内存安全 > 新增特性

8.3 对整个生态的影响

Bun 迁移的影响远超 Bun 本身:

  1. Rust 生态:Bun 证明了 Rust 可以胜任复杂的生产级 JavaScript 运行时
  2. Zig 生态:失去最成功的项目之一,Zig 社区需要重新证明自己的工程价值
  3. AI 重写领域:Bun 提供了迄今为止最大规模的 AI 辅助代码迁移案例
  4. JavaScript 运行时竞争:Deno (Rust) vs Bun (Rust, formerly Zig) vs Node.js (C++/V8)

结语

Bun 从 Zig 到 Rust 的迁移,是2026年最值得记录的技术事件之一。它不仅是代码的迁移,更是一场关于工程哲学的公开辩论:AI 能否写生产级代码?系统级软件该选什么语言?编译期保证和运行时灵活性的边界在哪里?

六天后,这条推文引发了一场技术圈地震:

"如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本。"

这场地震的余波,还在持续。


参考来源

  1. Jarred Sumner X (@jarredsumner), 2026年5月7日-5月11日
  2. Bun GitHub Issue #33453 - Claude Code Memory Leak
  3. Simon Willison Blog - Claude Code + Rust Bun (2026年7月19日)
  4. Theo (t3.gg) X, 2026年5月12日
  5. Bun 官方博客 - "Rewriting Bun in Rust" (2025年底)
  6. Reddit r/rust - Bun Memory Leak Discussion (2026年3-4月)
  7. IT之家 - Claude Code 已整合 Rust 重构版 Bun (2026年7月24日)

推荐文章

前端开发中常用的设计模式
2024-11-19 07:38:07 +0800 CST
Vue3中的自定义指令有哪些变化?
2024-11-18 07:48:06 +0800 CST
Vue3中的v-slot指令有什么改变?
2024-11-18 07:32:50 +0800 CST
Go中使用依赖注入的实用技巧
2024-11-19 00:24:20 +0800 CST
全新 Nginx 在线管理平台
2024-11-19 04:18:33 +0800 CST
Go语言中的mysql数据库操作指南
2024-11-19 03:00:22 +0800 CST
程序员茄子在线接单