编程 六天、96万行Rust、Bug修Bug:一次被内存泄漏逼出来的语言迁移

2026-07-23 13:14:41 +0800 CST views 6

六天、96万行Rust、Bug修Bug:一次被内存泄漏逼出来的语言迁移

2026年5月,编程圈上演了一出足以载入史册的荒诞剧:JavaScript运行时Bun的创始人Jarred Sumner在X上轻描淡写地发了一条推文——"如果我们合并Rust重写版本,这将是Zig的最后一个版本。"就这样,一个曾经以Zig语言为傲、四年积累96万行代码的明星项目,在六天内被AI亲手"换心",然后被创造者用一句话宣告了旧生命的终结。

这不是一次普通的版本迭代。这是一场关于语言哲学的公开决裂,一次AI辅助大规模软件重写的工程实验,也是关于"什么是生产级代码"这个老问题的最新注脚。

今天我们就来完整复盘这场迁移:从Bun为什么当初选择Zig,到内存泄漏问题如何一步步把Claude Code逼到23GB虚拟内存,再到Claude如何在六天内生成96万行Rust代码并让它们跑起来,最后聊聊这场迁移背后那些被忽视的工程细节。

一、背景:一个"完美选择"是怎么变成噩梦的

要理解这场迁移的戏剧性,得先知道Bun当初为什么选Zig。

Bun诞生于2022年,定位是Node.js的替代品——更快的启动速度、更强的打包/测试能力、兼容Node.js API的同时在性能上全面超越。技术选型时,Jarred Sumner做了一个在当时看起来相当有远见的选择:用Zig来写核心运行时。

这个选择有充分的理由。Zig是一门强调"没有隐藏控制流、没有隐藏内存分配、没有宏 magic"的系统编程语言,语法简洁,编译控制力强,特别适合写需要精细控制内存和调用C库的场景。相比Rust,Zig的学习曲线更平缓;相比C++,它的现代工具链和编译模型更友好。对于一个需要极致性能的JavaScript运行时来说,这是一个看起来很理想的技术选型。

Bun确实没有让人失望。在很长一段时间里,Bun凭借Zig带来的性能优势,在JavaScript运行时的竞争中脱颖而出,与使用C++的Node.js、使用Rust的Deno形成了三足鼎立的格局。对于Zig社区来说,Bun几乎成了Zig在现代基础设施世界里的"活广告"——一个用Zig写出来的、能真正跑在生产环境里的高性能项目。

但问题也在悄然累积。

二、危机:4700个Issue和23GB的Claude Code

Bun被Anthropic收购是2025年12月的事。官方说法是"加速Claude Code能力",本质上是要让Bun成为Claude Code背后的运行时、包管理器、bundler和测试工具。Anthropic将Bun定义为"AI驱动软件工程的重要基础设施"。

从理论上讲,这是一个双赢的选择。Claude Code是一个以Bun可执行文件形式发布的命令行工具——当你安装Claude Code时,你实际上也在运行Bun。Anthropic评估过很多运行时方案,最终选择了Bun,核心原因Bun的启动时间只有3毫秒左右,而Python要慢15倍。对于一个AI编程助手来说,这意味着用户体验是"丝滑响应"还是"明显卡顿"的本质区别。

但现实很快给这个"完美选择"泼了一盆冷水。

2026年3月12日,一个编号#33453的Issue被提交到Claude Code仓库,内容直指核心问题:

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

这意味着问题出在Bun用C/C++写的底层代码里,而非JavaScript层。这让修复变得尤为困难——内存泄漏不在JavaScript引擎中,而是在底层的WebKit Malloc或Bun自身的C/C++ FFI层中。

更夸张的记录来自另一个被机器人标记为重复并关闭的Issue:运行14小时后,Claude Code进程占用23GB虚拟内存,CPU占用143.8%,整个系统完全卡死。

与此同时,Bun自身的问题列表也在持续膨胀。Reddit用户Xtergo曾做过一个对比:Node.js作为几乎"驱动整个互联网"的运行时,目前大约有1700个open issues;而更年轻、用户规模远小于Node.js的Bun,却已经积累了约4700个open issues。Node.js承担着全球级别的工作负载,却维持着更小的backlog;而仍处于早期阶段的Bun,却已经被问题淹没了。

这4700个open issues中,有相当一部分可以追溯到同一个根本原因:Zig语言本身的一些特性,使得某些类型的内存安全问题变得难以从根本上解决。

三、根源:Zig的no-AI policy与Bun的现实困境

Bun和Zig社区之间的关系,在这次迁移前已经出现裂痕。

Bun团队此前其实已经fork过Zig。他们曾宣称,通过在macOS与Linux上引入LLVM并行代码生成,debug编译速度提升了四倍。但这些优化始终无法upstream回Zig官方。原因之一,是Zig社区有一条极其严格的"no-AI policy"——禁止AI生成issue、禁止AI生成PR,甚至禁止AI在社区中参与讨论。

Zig基金会成员Loris Cro曾公开表示,大量LLM贡献只会制造"幻觉PR"、"垃圾噪音"以及动辄上万行、根本无法维护的提交。这种立场本身无可厚非——在2024年之前,这个担忧确实是有远见的。

但讽刺的是,Anthropic本身就是整个AI coding浪潮最激进的推动者之一,而Claude Code现在又深度依赖Bun runtime。结果出现了这样的局面:一边是Zig社区全面封禁AI生成代码,另一边却是Bun团队开始用Claude agent大规模把Zig本身迁移出Zig。

这不是简单的技术决策,而更像是两种软件工程哲学的正面碰撞。

Jarred Sumner本人在5月10日的一次推文中透露了真正的心声:

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

Rust恰恰提供了这样的工具:编译期内存安全检查、所有权系统、生命周期分析。这些Zig难以提供的保证,正是Bun长期挣扎于4700个open issues的根本原因之一。

四、重写:六天,96万行,99.8%测试通过

2026年5月初,Bun的GitHub仓库里出现了一个名为claude/phase-a-port的新分支。分支内部,数十万行由Claude生成的Rust代码,和原始Zig实现并排存在。

整个迁移有一套极其详尽的文档PORTING.md,长达576行,把Zig到Rust的迁移拆成Phase A和Phase B两个阶段:

Phase A:忠实翻译,不求编译通过
Claude的首要任务是将Zig代码逐文件忠实地翻译为Rust逻辑,这个阶段甚至不要求生成的Rust代码能编译通过。这个策略背后的逻辑很清晰:在保证语义等价性的前提下,先完成量,再处理质。

Phase B:逐crate解决编译、构建和运行问题
在Phase A积累了足够多的翻译产物后,再逐个crate地解决编译错误、链接问题和行为偏差。

文档还做了非常细致的规定:

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

这种规范约束,本质上是把AI的角色定位为一个"超级翻译器"而非"独立决策者"——AI负责将已有的、经过验证的Zig逻辑翻译成Rust,而不是从头设计解决方案。

5月7日,Jarred Sumner发推更新进度:Rust迁移已经涉及约4000次commit、96万行代码,当时只剩下3个编译错误。Rust版本已经能显示help menu,bun run和package.json scripts也已经跑起来——这意味着JSON parser、AST、logger、module resolver、文件系统遍历、模块解析缓存等一整串基础能力都已经被迁移过去。

Jarred当时明确表示:当前状态仍然只是"勉强能动",绝对不能交付。

5月9日,进度跳到了另一个量级:Rust版本在Linux x64 glibc环境下通过了Bun既有测试套件的99.8%。

到这时,整个迁移只用了大约六天。

五、数据对比:73个unsafe vs 13000个unsafe

重写完是重写完了,但质量到底怎么样?

最大的争议来自t3.gg创始人Theo的一个发现,他将Bun的Rust移植版与Python包管理器uv进行了对比:uv包含35万行Rust代码,以及73个unsafe调用;而Bun的Rust移植版已经有68.1万行Rust代码,并且有超过13000个unsafe调用。

73 vs 13000,差了接近180倍。

这个数字一出来,社区瞬间炸开了锅。

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

他的解释在技术上有一定道理。Bun并非一个纯粹的Rust项目——它内部包含了大量JavaScriptCore引擎(WebKit的JS引擎)、libarchive、OpenSSL、SQLite等C/C++库,这些库与Rust的FFI交互天然需要unsafe。而uv是一个相对纯粹的Rust项目,不存在这样的底层依赖。

但社区的担忧并不只是数字本身。

网友Aashish Ranjan Singh写道:"UV rust是由真正的开发人员编写的,每一行代码都经过了审查。Bun rust由Agents编写,由Agents审核,并由Agents批准和合并。完全在意料之中的结果。"

另一位开发者HSVSphere更直接:"uv不是vibecoded的垃圾,而且开发它的人对Rust非常了解。但Bun就完全不同了,它简直是一场风格灾难。用Deno吧。"

这些批评触及了这次迁移最核心的争议:当代码由AI在六天内生成,并且整个审查流程(从提交到合并)也由AI主导时,我们对代码质量的期待应该是什么?

六、架构分析:Rust版本到底改变了什么

抛开社区争议,从纯技术角度审视这次迁移的结果,我们能发现一些有趣的发现。

二进制体积:缩小3-8MB

Rust版本的二进制文件比Zig版本小了3-8MB。这个数字猛一看有些反直觉——Rust以生成较大的二进制文件著称。缩小体积的原因可能包括:

  1. Zig编译器生成的目标代码与Rust编译器LLVM后端在优化策略上有差异
  2. Rust的链接器(lld或系统链接器)在处理C/C++ FFI时可能有不同的内联策略
  3. 部分Zig版本中为调试信息预留的空间在Rust中被优化掉了

async Rust的刻意回避

迁移文档明确规定"禁止使用tokio/rayon/hyper/futures"和"禁止async fn"。这个限制初看有些奇怪——Rust的异步生态以tokio为核心,为什么不用?

答案在于Bun自身的架构。Bun的event loop是完全自己实现的,不依赖任何外部async runtime。Bun的JavaScript运行时需要完全控制调度策略,包括微任务队列、宏任务队列、timer管理、I/O多路复用等。如果引入tokio,Bun就要在两个event loop之间做协调,这会引入不必要的复杂性和性能损耗。

所以Rust版本的Bun采用了与Zig版本相同的策略:所有I/O操作和并发调度都在Bun自己的event loop框架内完成,不引入外部async runtime。

tagged pointer的Rust化

Jarred在迁移过程中发推询问Rust社区一个问题:Bun的Zig代码大量使用tagged pointer来处理event loop task、进程退出回调、非阻塞文件I/O等接口;迁到Rust后,如果直接用trait或函数指针,可能会带来额外开销。他在寻找一种既不影响性能、又更适合Rust所有权的实现方式。

这个问题触及了系统编程中最经典的空间换时间vs类型安全权衡。Zig的tagged pointer本质上是一种"把元数据编码到指针本身"的技术——例如,用指针的低位或高位存储类型标签或引用计数。这在Zig中是完全合法的,因为Zig允许直接的位操作。但在Rust的所有权模型下,这需要更小心地处理——通常需要用NonNull<T>配合手动生命周期管理,或者用enum加std::mem::size_of的技巧。

内存安全的实际收益

Rust版本最核心的价值,不是性能提升,而是可预测性。Rust的所有权和借用检查器虽然不能完全消除内存泄漏(Rust允许你写逻辑上泄漏内存的代码),但它能保证不存在数据竞争、不存在use-after-free、不存在double-free这些类型的安全问题。对于一个需要长期运行、不希望内存持续增长的CLI工具来说,这种编译期保证比任何运行时profiling工具都更有价值。

七、unsafe的真相:13000个调用都是什么

深入分析Bun Rust版本中的unsafe调用来源,实际上可以分为几类:

第一类:与C/C++库的FFI绑定(不可避免)

这是最大的来源。Bun的核心包含了:

  • JavaScriptCore引擎:C++,与Rust的绑定需要unsafe
  • SQLite:C,与Rust的绑定需要unsafe
  • OpenSSL:C,与Rust TLS库的绑定需要unsafe
  • libarchive:C,与Rust archive库的绑定需要unsafe

这类unsafe是无法消除的,因为底层库本身就是用C/C++写的,任何Rust对它们的调用都跨越了内存安全的边界。

第二类:底层系统调用

Rust的标准库在某些底层操作上仍然需要unsafe,例如:

  • 直接调用mmap/munmap做内存映射
  • 调用socket系统调用
  • 访问/proc或/sys文件系统接口

第三类:与JavaScript引擎的集成

JavaScriptCore(WebKit的JS引擎)是一个极其复杂的C++项目。Bun需要从Rust中调用它的API来执行JavaScript代码、管理JS堆、与JS对象交互。这些FFI调用都需要unsafe

第四类:性能关键的零拷贝操作

某些场景下,为了避免数据复制,需要用unsafe直接操作指针。这类unsafe通常有详细的SAFETY注释,说明其前提条件。

理解了这些分类,就不难理解为什么Bun的Rust版本需要如此多的unsafe调用——它的技术栈本身就包含了大量的C/C++依赖,这不是语言选择的问题,而是技术债的问题。

八、工程启示:AI写代码的边界在哪里

这次迁移给整个行业提出了一个严肃的问题:当AI可以在六天内生成96万行功能性代码时,我们如何定义"代码质量"?

AI擅长做什么

从Bun的案例来看,AI最适合的场景是:代码的形式化翻译。即有明确的源语言实现、明确的语义等价性要求、明确的边界约束。Phase A的策略——"忠实翻译,不求编译通过"——恰好把AI的优势发挥到了最大:模式匹配、语法转换、逐文件翻译,这些正是LLM最擅长的任务。

AI不擅长做什么

但AI在以下方面仍然存在明显不足:

  1. 架构决策:Bun的event loop设计、内存管理策略、调度模型,这些是四年积累下来的工程决策,不是六天的翻译能替代的。AI保留了原有架构,但也保留了原有的架构缺陷。

  2. 边际情况处理:AI生成的代码在正常路径上通常表现良好,但边界条件、异常处理、错误恢复等场景往往是薄弱环节。这需要人工review和经验积累来补足。

  3. 库的选择和依赖管理:迁移文档规定了"禁止使用tokio"等约束,这是人类架构师的决策。如果完全放手让AI选择依赖,后果可能是一场依赖地狱。

一个值得深思的现象

Bun Rust版本最终被直接合并到了主分支,这意味着这些由AI生成、经过AI review的代码,在没有充分人工审查的情况下就进入了生产级项目。这在传统的软件工程实践中是不可接受的——通常至少需要两个有经验的人类工程师进行代码review。

但Jarred Sumner似乎对此并不担心。他真正在乎的是结果:测试套件通过了吗?性能达标了吗?内存泄漏修好了吗?

九、实际影响:Claude Code真的变好了吗

说了这么多,读者最关心的问题可能是:迁移之后,Claude Code的内存问题真的解决了吗?

从技术逻辑上推演,答案是有可能,但不确定

Rust版本解决的是Bun runtime层的内存安全问题。JavaScript引擎(JavaScriptCore)仍然是C++代码,内存泄漏仍然可能发生在JS引擎内部。但Rust版本让Bun团队能够更精确地定位问题——因为Rust的类型系统和借用检查器可以在编译期消除某些类别的内存错误,使得运行时出现的任何泄漏都更可能是逻辑问题而非系统编程错误。

从社区反馈来看,迁移后的Bun在内存占用和稳定性方面确实有所改善,但具体的长期数据还需要更多时间观察。

十、这对其他项目意味着什么

Bun的案例给整个行业提供了几个值得参考的经验:

经验一:AI辅助迁移的可行路径

Bun展示了"AI辅助大规模代码迁移"的一种可行范式:不是让AI从零设计,而是让AI在严格约束下做形式化翻译,然后通过人类定义的测试套件来验证正确性。测试套件是这次迁移成功的关键——没有99.8%的测试通过率,任何AI生成的代码都无法让人有信心合并到主分支。

经验二:语言选择是战术问题,不是战略问题

Bun四年用Zig,今年用Rust,明年可能还会变。真正不变的是团队对性能、稳定性和开发效率的追求。当一个语言/工具不再服务于这个目标时,换掉它是理性的选择,而不是对过去决策的否定。

经验三:unsafe debt是可以接受的

对于需要大量FFI的项目(如嵌入式、游戏引擎、AI推理等),一定量的unsafe代码是不可避免的。关键不是消除所有unsafe,而是:

  • 清楚地标注所有unsafe的边界
  • 为每个unsafe写详细的SAFETY注释
  • 尽可能在unsafe外围提供安全的抽象层
  • 建立测试和fuzzing来覆盖unsafe路径

经验四:AI coding的质量需要新的度量标准

传统的代码质量度量(行数、圈复杂度、测试覆盖率)对于AI生成的代码可能并不适用。也许我们需要新的指标:语义一致性、边界条件覆盖率、对抗性测试通过率……这些指标还没有成熟的工具来测量,但这是行业需要开始探索的方向。

结语

Bun的Rust迁移,是2026年最值得关注的工程事件之一。它不是一个"AI取代程序员"的故事,也不是一个"AI全是垃圾代码"的证明。它是一个关于约束、验证、权衡和持续改进的真实案例——只不过恰好,代码的作者是AI,reviewer也是AI,最终合并代码的决策者是人类。

未来,也许会有更多的项目走上类似的路径:人类定义目标和约束,AI负责执行,长期积累的测试套件负责验证。在这个模型中,测试套件的重要性被提升到了前所未有的高度——它不再只是质量保证工具,而成了AI时代的"合约",是AI和人类之间唯一的信任基础。

对于普通开发者来说,最重要的启示也许是:持续建设你的测试套件,因为它不只是在今天帮你发现Bug,在AI接管代码生成的未来,它可能是你最重要的防线。


本文技术信息基于Bun官方公告、GitHub仓库变更记录、社区讨论及Jarred Sumner公开推文。部分细节(如具体commit数量、代码行数)可能因统计口径不同而存在差异。

复制全文 生成海报 Bun Rust Zig AI编程 性能优化

推荐文章

前端如何一次性渲染十万条数据?
2024-11-19 05:08:27 +0800 CST
2024年公司官方网站建设费用解析
2024-11-18 20:21:19 +0800 CST
JavaScript 流程控制
2024-11-19 05:14:38 +0800 CST
PHP 代码功能与使用说明
2024-11-18 23:08:44 +0800 CST
程序员茄子在线接单