三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

从Zig到Rust:Bun运行时百万行重写的架构决策、AI工程实践与开源治理之争

从Zig到Rust:Bun运行时百万行重写的架构决策、AI工程实践与开源治理之争

2026年7月8日,Bun创始人Jarred Sumner发布官方博客《Rewriting Bun in Rust》,正式宣布这个月下载量超过2200万次的JavaScript运行时将底层语言从Zig全面迁移到Rust。Bun v1.3.14成为最后一个以Zig为主的发行版本,v1.4.0起内核全部采用Rust重构。整个重写过程仅耗时约11天,一名工程师借助Claude完成了535,496行Zig代码到Rust的机械化迁移,新增超过100万行代码,6,755次提交,99.8%的现有测试通过。

这不仅仅是一次编程语言的切换。Bun作为Node.js的drop-in替代品,承载着JavaScriptCore引擎、HTTP/WebSocket服务器、包管理器、测试运行器和打包器等庞大功能集,其底层语言的选择直接影响整个JS工具链的稳定性天花板。迁移背后揭示的是一个被长期忽视的工程难题:当垃圾回收(GC)与手动内存管理在同一进程内深度交织时,现有系统级语言都无法提供足够的安全保障。

本文将从技术动因、AI辅助迁移方法论、对抗式代码审查流程、以及随后爆发的Zig社区反击四个维度,深度拆解这场2026年最重大的开发者工具链事件。

一、为什么离开Zig:GC与手动内存管理的交叉地带

Bun的核心是一个嵌入JavaScriptCore(JSC)的运行时。JSC是Safari的JavaScript引擎,内部依赖精确的垃圾回收器来管理JS对象生命周期。Bun自身用Zig编写的原生绑定层需要在JSC的GC世界和手动管理的C/C++库之间不断搬运数据——这种「GC与手动内存混合」的模式是问题根源。

Jarred在博客中直接点明了核心矛盾:"Zig和C一样不为你管理内存,这对很多项目来说是使用Zig的好理由。但JavaScript是垃圾回收语言,现代JS引擎对异常处理和GC有严格规则。在Bun中,正确处理GC值和手动管理值的生命周期一直是稳定性的主要问题来源。"

Zig的内存管理哲学是"无隐藏控制流":它用显式的defererrdefer关键字在作用域末尾执行清理,而非C++的隐式析构函数或Rust的Droptrait。这种设计在纯手动内存管理场景下简洁优雅,但在GC混合场景下暴露了系统性短板。Jarred列举了Zig方案在实践中遇到的核心困境:

// Zig方案:需要手动跟踪共享指针的引用计数 fn foo(a_ptr: SharedPtr(TCPSocket)) !void { const a: *TCPSocket = a_ptr.get(); defer a_ptr.deref(); // 必须显式释放 const b = try do_something_with_a(a); defer b.deref(); // 又一个手动释放 // 问题:如果do_something_with_a抛出错误, // a_ptr.deref()和b.deref()的执行顺序和次数是否正确? // 当同一个*T被传给多个函数时,何时才能安全清理? }

Bun团队曾考虑在Zig中实现Rust风格的智能指针(SharedPtr)来规范内存管理,但Jarred承认:"自研智能指针的工具体验比Rust更差,却没有任何Rust的保证。"Zig社区贡献者Loris Cro在5月31日发表的关于断言使用方式的文章,虽然未点名Bun,但暗示"一个最近离开Zig生态的热门项目"将错误的断言模式带入了生产环境。

二、Bun的Bug图谱:use-after-free与内存泄漏的系统性问题

博客中公开的v1.3.14版Bug列表令人触目惊心。这些问题并非边缘case,而是深入核心模块的内存安全缺陷:

  • node:zlib的heap-use-after-free:在异步.write()仍在线程池执行时调用.reset(),触发已释放堆内存的访问
  • node:http2的use-after-free:重入式JS回调(如在timeout监听器中调用session.request())触发hashmap rehash,使内部流指针失效
  • UDPSocket.send()的use-after-free:用户代码在valueOf()toString()回调中分离ArrayBuffer,导致载荷捕获与实际发送之间的内存失效
  • Buffer#copy的越界读valueOf回调在参数强制转换期间分离或调整底层ArrayBuffer大小
  • crypto.scrypt的内存泄漏:回调函数和受保护密码/盐缓冲区在输出缓冲区分配失败时永不释放
  • tlsSocket.setSession()的内存泄漏:每次调用泄漏约6.5KB的SSL_SESSION对象
  • fs.watch()的引用计数下溢:watcher对象永远不被GC回收,每个都被永久固定为GC根
  • MessageEvent的竞态条件崩溃:GC标记线程在并发访问中观察到撕裂的variant

这些Bug有一个共同模式:它们都源于GC对象与手动管理内存之间的生命周期不匹配。在Zig中,每一个内存分配都需要人工审查"这些字节在哪里释放?如何确保只释放一次?是否正确检查了JS异常?这个指针对保守栈扫描器可见吗?"

Jarred坦言:"我们的Bug修复清单让人感觉很糟,我厌倦了睡前还要担心Bun的崩溃。"团队已经在做的防护措施包括:给Zig编译器打补丁以支持Address Sanitizer(每次提交都运行ASAN测试)、在Windows上发布ReleaseSafe安全检查构建、使用Fuzzilli进行24/7模糊测试。但这些措施都是在代码合并后才发现问题,而非在编译时预防。

三、为什么是Rust:编译器错误优于风格指南

面对系统性内存安全问题,Bun团队评估了三条路径:

路径一:Zig + 风格指南。通过明确的代码规范来约束内存管理,类似于TigerBeetle的TigerStyle或Google的31,000字C++风格指南。问题在于执行——"如何确保风格指南被遵守?历史上,代码审查是答案,配合linters和静态分析器的尽力而为。"

路径二:C/C++。Bun约20%的代码已经是C++(JSC、uWebSockets、lshpack/lsquic、BoringSSL、SQLite)。切换到C++可以获得构造/析构函数,删除大量extern "C"包装代码。但仍然依赖风格指南来执行内存安全,即使有ASAN,内存损坏和泄漏仍会发生。

路径三:Rust。在safe Rust中,use-after-free、double-free和"错误路径中忘记释放"都是编译器错误。Droptrait提供RAII式自动清理。编译器错误是比风格指南更好的反馈循环。

// Rust方案:所有权系统在编译时保证内存安全 fn foo(a: &TCPSocket) -> Result<(), Error> { let b = do_something_with_a(a)?; // b在作用域结束时自动Drop,无需手动释放 // a是借用引用,编译器保证其在foo执行期间有效 // 如果do_something_with_a返回Err,b仍然正确清理 Ok(()) }

Jarred的判断标准很清晰:知道问题越早越好。模糊测试在代码合并后运行,CI在推送时运行,运行时安全检查和ASAN在代码运行时(希望在开发阶段、CI之前)运行。而Rust的编译器错误在编写代码的那一刻就出现——这是最早的反馈点。

历史经验表明,大规模重写通常是糟糕的主意。Bun有535,496行Zig代码(不含注释),用一个小团队在另一种语言中重写需要整整一年,意味着一年内冻结Bug修复、安全补丁和功能开发。但AI改变了这个方程式。

四、11天百万行:Claude辅助迁移的工程方法论

整个迁移过程的规模令人震惊:约50个动态工作流(dynamic workflows)在Claude Code中连续运行11天,使用Anthropic的预发布模型Claude Fable 5,产出1,009,257行代码,6,755次提交。成本约16.5万美元,使用了64个Claude实例并行工作。

Jarred在方法论上做了两个关键决策:

决策一:一次性全量重写,而非增量迁移。基于他此前将esbuild从Go移植到Zig的经验,增量重写会引入大量临时代码,在短期到中期内造成痛苦。全量重写虽然风险更高,但避免了双语言并行维护的复杂性。

决策二:机械化移植,最小化行为变更。重写后的Rust代码在架构、性能和功能集上与原Zig版本保持一致,使用完全相同的测试套件。Bun的测试套件用TypeScript编写,不依赖运行时的编程语言,这成为跨语言验证的关键基础设施。

// 伪代码:每个动态工作流的核心循环 let task; while ((task = todoList.pop())) { const result = task(); const feedback = await Promise.all([review(result), review(result)]); await apply(feedback, result); }

50个动态工作流各司其职:生成Zig到Rust的模式映射指南、机械化移植每个.zig文件到.rs文件、修复每个crate的编译器错误、让bun testbun build等子命令工作、让整个测试套件通过、以及多轮大规模重构和清理。

关键在于,这不是简单的"提示Claude重写Bun,然后祈祷它能工作"。Jarred全程监控工作流输出,手动阅读结果以检查问题和Bug,并提示Claude修改循环逻辑来修复问题。

五、对抗式审查:如何信任LLM生成的代码

面对一个新增超过100万行代码的PR,如何建立足够的信心来负责任地合并大量LLM编写的代码?Jarred给出的答案是三重保障:

第一重:语言无关的百万断言测试套件。Bun的TypeScript测试套件包含上百万个断言,完全独立于运行时实现语言。这是跨语言迁移能够验证正确性的根基。

第二重:对抗式代码审查(Adversarial Review)。核心原则是分离实现者和审查者的上下文窗口。写代码的Claude希望代码被接受(存在确认偏差),审查代码的Claude的唯一任务是找Bug。每个实现者配备2个或更多对抗式审查者,实现者不审查,审查者不实现。

博客展示了对抗式审查捕获的典型Bug:

// Bug 1: 异步close导致的use-after-free + double-free // 审查者发现:uv_close是异步的,libuv保留原始句柄指针直到下一个loop tick // 但pipe是Box,在match arm结束时Drop——libuv持有已释放内存 // 修复:Box::leak(pipe).close(Subprocess::on_pipe_close) // Bug 2: 负时间戳的无效timespec // 审查者发现:对于1970年前的文件mtime(负数非整数时间), // trunc向零取整导致-1.5变成{sec: -1, nsec: -500_000_000} // 负nsec是无效timespec,应使用floor

第三重:修复流程而非手修代码。当发现问题时,修改生成代码的流程(调整提示词、工作流逻辑),而不是手工修补单个Bug。这确保同类问题在后续生成中被系统性预防。

这套方法论的核心洞察是:将日常工程工作简化为"写代码-审查-应用反馈"的循环,然后用AI来并行化这个循环。但关键的人机协作节点——监控、判断、决策合并——仍然由人类工程师承担。

六、Zig社区的反击:Andrew Kelley的另一种叙事

Bun的迁移文章发布后第二天,Zig创始人Andrew Kelley发布《My Thoughts on the Bun Rust Rewrite》,将一次语言迁移变成了代码质量、开源权力与AI生成代码的公开争论。

Andrew的核心论点不是比较Rust与Zig谁更好,而是质疑Bun是否有资格用自己的代码库来评价Zig。在他看来,Bun的麻烦主要来自自身的工程方式:临时补丁越积越多,断言和comptime被错误使用,技术债没有及时处理。Bun把这些写进迁移复盘,读起来却像是在让Zig替项目管理背锅。

他还透露,Zig Software Foundation早已认为Bun带来的负担超过价值,Bun转向Rust反而让团队松了一口气。2023年Bun向基金会捐赠了58,666.67美元(接近每年6万美元赞助规模),但Andrew称2025年12月Anthropic收购Bun后,定期赞助停止,月度会议也不再参加。

双方的分歧可以追溯到2022年8月Oven获得700万美元融资之后。Bun作为创业公司核心产品需要快速发布功能、补齐LSP和VS Code支持;Zig作为一门快速演进的系统编程语言,需要处理语言语义、编译器正确性和长期维护。一个重要商业用户希望功能尽快上线,不等于这项需求应该压过整个语言的路线。

2026年4月的编译器fork事件是公开决裂的导火索。Bun维护自己的Zig编译器fork,称修改后debug build速度提高四倍以上,但没有按常规路径提交上游。Bun给出的理由之一是Zig项目严格限制LLM生成的贡献;Zig核心贡献者则回应称相关并行语义分析可能引入非确定性,现有语言约束还不足以保证结果正确。

Andrew随后修改了文章,删除了部分过于绝对的措辞,向担心未来被Zig项目同样公开对待的用户道歉。但他的核心判断没有改变:Bun的迁移理由不能脱离Bun自己的工程选择来讨论。这场争论比一般语言迁移更难收场,因为双方不是在讨论可复现的benchmark,而是在重算多年合作的总账。

七、对JS运行时生态的深远影响

Bun迁移到Rust对JavaScript运行时生态产生了多层影响:

对Node.js生态。Bun的CLI月下载量超过2200万次,Claude Code和OpenCode等工具依赖Bun作为运行时。Rust的内存安全保证意味着这些工具的稳定性天花板被系统性提升。Bun v1.3.14的二进制体积比Zig版本缩小3-8 MB,性能在所有平台上均达到或超过原有水平。

对Zig生态。Bun长期是Zig最醒目的生产案例。Bun的离开虽然在短期内是负面信号,但也促使Zig社区反思语言在GC混合场景下的适用边界。Andrew Kelley的回应表明Zig团队并不认为这是语言的失败,而是使用方式的问题。

对AI辅助工程。11天完成百万行代码迁移的案例,加上Anthropic博客中提到的另外9个类似规模的迁移项目,标志着AI辅助大型代码库重写从实验进入了工程实践阶段。Bun的对抗式审查方法论——分离实现与审查上下文、语言无关测试套件、修复流程而非手修代码——为行业提供了可复制的模板。

对运行时竞争格局。Deno同样使用Rust编写,Bun的加入意味着两个主要Node.js替代品都选择了Rust作为底层语言。这进一步巩固了Rust在系统级JS基础设施领域的地位,同时也提出了一个问题:当JavaScriptCore(C++)仍然是引擎层时,Rust绑定层的内存安全保证能否完全覆盖引擎本身的C++代码风险?

八、局限性

本文的分析基于公开的博客文章、GitHub仓库和社区讨论。以下方面存在局限:

  • Bun与Zig团队之间2022-2025年的私下沟通细节主要来自Andrew Kelley的单方回忆,Bun未逐项公开回应,相关叙事的完整性无法独立验证。
  • 99.8%测试通过率意味着仍有0.2%未通过的测试,这些失败案例的具体内容和影响范围在博客中未详细说明。
  • "性能在所有平台上均达到或超越原有水平"这一声明来自Bun官方,缺乏第三方独立基准测试的交叉验证。
  • AI辅助迁移的成本(16.5万美元)和效率数据来自Jarred Sumner的叙述,实际工作流中人类工程师的隐性投入(监控、调试、决策)可能未被完全计入。

九、结论

Bun从Zig到Rust的迁移是2026年开发者工具链领域最具标志性的事件。它证明了三件事:第一,GC与手动内存管理的混合是系统级语言尚未解决的难题,Rust的所有权系统在编译时提供的安全保证是当前最优解;第二,AI辅助的大型代码库重写已经从概念验证进入工程实践,对抗式审查方法论为此提供了可信度框架;第三,开源项目中商业用户与语言维护者之间的目标分歧,是比技术选型更难解决的组织问题。

Bun的Rust版本已进入canary渠道,v1.4.0正式版即将发布。对于前端开发者而言,这意味着更稳定的运行时和更少的内存安全崩溃;对于开源社区而言,这场迁移留下的工程方法论和治理争论将持续影响未来的技术决策。

相关代码与可视化实验项目已开源:GitHub - wangzifan396-wzf/TW: AI 可视化实验室集 · 600 个交互式项目 · 4960+ 模块 · 零外部依赖 · 纯 HTML/CSS/JS + SVG · 覆盖 AI/ML、CS 系统、计算理论、交叉学科全谱系 · GitHub

← 返回列表