Bun又用Rust重写:借助AI对抗工作流11天改写50万行 当Bun从Zig跳槽到Rust借助AI对抗工作流11天改写50万行代码这操作把我看傻了Bun这个JavaScript运行时从Go抄到Zig现在又从Zig跳槽到Rust整个过程只用了11天6502次提交峰值时每分钟产出1300行代码。Bun最初选择Zig的理由特别简单Jarred在2021年4月16日写下第一行Zig代码。他当时在Hacker News上看到Zig语言参考文档单页设计没有花里胡哨的东西立马被那种对底层控制和性能的执着吸引了。Zig没有隐式内存分配没有构造函数析构函数一切都要你手动用defer来清理。这种风格特别适合写底层基础设施。Bun一开始就是个野心巨大的项目。JavaScript和TypeScript的转译器、压缩器、打包器npm兼容的包管理器Jest风格测试运行器Node.js模块解析HTTP和WebSocket客户端还有fs、net、tls这些Node.js API实现。全部塞进一个工具里。Jarred在奥克兰一个拥挤的公寓里花了一年时间没有大语言模型帮忙硬生生用Zig写出了Bun的第一个版本。这种项目通常的结局就是躺在GitHub个人主页的墓地项目列表里。Zig让Bun活了下来。现在Bun每月下载量超过2200万。Claude Code和OpenCode这些工具把Bun当成运行环境Vercel、Railway、DigitalOcean都提供原生支持。Bun从一个个人项目长成了基础设施级别的工具。规模大了之后Zig的短板开始暴露Bun v1.3.14修复了一长串崩溃和内存泄漏。node:zlib里调用reset方法时线程池还在执行异步写入堆内存被释放后继续使用。node:http2里JavaScript回调触发哈希表重新哈希内部流指针失效。UDPSocket.send里用户代码在valueOf回调中把ArrayBuffer分离了导致内存越界写入。crypto.scrypt里输出缓冲区分配失败时回调和受保护的密码缓冲区永远不会释放。这只是冰山一角。Bun团队已经做了很多努力给Zig编译器打补丁支持Address Sanitizer每次提交都跑测试套件Windows上使用ReleaseSafe构建24小时用Fuzzilli做模糊测试还有大量端到端内存泄漏测试。这些措施比大多数项目都强了。但Jarred累了。每次睡觉前都在担心Bun的崩溃问题。他不怪Zig其他Zig用户没有遇到这些问题。Bun的特殊之处在于它要把垃圾回收的内存和手动管理的内存混在一起用。JavaScript引擎有严格的异常处理和GC规则Zig像C一样不帮你管理内存这种组合太容易出问题。每个内存分配都要仔细审查这些字节在哪里释放怎么确保只释放一次JavaScript异常检查正确吗这个GC指针对保守栈扫描器可见吗这是GC内存还是手动管理内存在Zig里defer需要在每个调用点手动添加。很容易忘记清理导致内存泄漏或者在极少触发的错误处理路径里执行两次清理导致双重释放。代码审查是主要防线配合linter和静态分析工具做尽力而为的检查。Bun团队考虑过用智能指针像Rust那样把所有权明确写在类型系统里。但Zig没有运算符重载写出来的代码会变成这样zigfn foo(a_ptr: SharedPtr(TCPSocket)) !void { const a: *TCPSocket a_ptr.get(); defer a_ptr.deref();和期望的Zig风格差太远。Bun想保持简洁不想让代码被所有权管理淹没。Rust为什么成了最终选择那个bug列表里大量问题是释放后使用、双重释放、错误路径忘记释放。在安全Rust里这些都是编译器错误。RAII风格的自动清理用Drop trait实现。编译器错误比代码风格指南的反馈循环好太多了。但重写一个53万行Zig代码的项目听起来像是自杀。排除注释535496行代码一个小团队要花整整一年。这意味着一年内不能修复bug、不能做安全更新、不能开发新功能。最稳妥的办法是机械地从Zig移植到Rust尽可能少改变行为用已有的测试套件验证。Bun的测试套件是用TypeScript写的不依赖运行时的编程语言。这个特性太关键了。Jarred的原本计划是加Rust风格的智能指针到Bun代码库通过风格指南强制执行来提升稳定性。但他内心其实不想这么做。自制智能指针的人机工程比Rust差还没有Rust的保证。然后他想到了一个疯狂的主意花一周时间测试Anthropic的新模型能不能把Bun重写成Rust。一开始他没抱期望。几天后测试套件很大一部分开始通过新Rust代码和原始Zig代码的匹配程度让他吃惊。他的态度从值得一试变成了我要合并这个。Claude重写Bun的全过程Jarred做这件事的方法和很多人想象的不一样。他并没有直接说Claude把Bun重写成Rust别犯错然后祈祷。他思考了人类会怎么做这件事。第一个大问题增量重写还是一次性全改根据他之前把esbuild转译器从Go移植到Zig的经验一次性全改更好。增量重写会引入临时代码短中期内会很痛苦。第二个问题怎么让Rust版的Bun保持原来的架构、性能和功能集怎么确保团队在重写后还能维护答案是做一个看起来像是把Zig代码翻译成Rust的东西。等v1.4发布后再逐渐减少unsafe使用让代码更符合Rust惯用法。执行层面Jarred用了大约50个动态工作流在Claude Code里连续跑了11天。每个工作流都是一个循环生成移植指南把Zig模式映射到Rust模式机械地把每个zig文件转成rs文件修复每个crate的编译器错误让bun test和bun build这些子命令跑起来让整个测试套件通过。最关键的是对抗性审查流程。每个实现者配两个对抗性审查员。审查员只有一个工作找出bug和代码不能工作的原因。实现者不审查审查者不实现。这三个bug是对抗性审查实际抓到的。第一个是异步关闭时的释放后使用加双重释放。pipe.close是异步操作libuv保留原始句柄指针直到下一次事件循环然后调用on_pipe_close释放内存。但pipe是Box在这个match分支结束时就会drop。libuv拿着已经释放的内存回调里又释放第二次。Box::leak修复了这个问题。第二个是负时间戳的转换。对于1970年之前的文件修改时间负数非整数时间trunc向零取整导致nsec为负值。无效的timespec结构。用floor取整让nsec落在0到1e9之间。第三个是color-mix里百分比的默认值处理。unwrap_or会急切求值参数即使第一个百分比存在第二个百分比的unwrap也会执行。被省略的百分比会触发panic。unwrap_or_else用闭包延迟求值解决了问题。这三个bug都编译通过了看起来都很合理。审查员是另一个Claude在自己的上下文窗口里只拿到diff没有任何实现者的推理过程专门来找茬。工作量到底有多大在开始写代码之前Jarred花了3个小时和Claude讨论怎么把Zig代码的模式映射到Rust。Claude把这些讨论序列化成一个PORTING.md文档。然后面临一个关键问题怎么给手动管理内存的代码加上Rust生命周期他让Claude启动一个动态工作流分析代码库中每个结构体字段的合适生命周期。读取每个文件里每个结构体字段追踪控制流找出生命周期复杂的字段提出生命周期建议用两个对抗性审查员审查应用反馈序列化成LIFETIMES.tsv供其他Claude参考。然后是试运行。在要求Claude翻译全部1448个zig文件之前先只翻译3个。每个文件一个实现者写rs文件两个对抗性审查员检查匹配度和移植指南遵从性一个修复者应用建议。实际执行时遇到了各种问题。Claude之间互相踩踏一个跑git stash另一个跑git stash pop然后git reset HEAD --hard。Jarred要求编辑工作流禁止跑任何git命令除非是提交特定文件。也不准跑cargo不准跑任何慢命令。峰值时64个Claude同时在跑。4个工作树每个16个Claude每个工作流里1个实现、2个审查、1个应用。6502次提交1695次提交每小时。峰值小时695次提交。编译器错误成了工作队列。cargo check输出大约16000个错误按crate分组分给64个Claude。最棘手的是循环依赖问题。Zig代码库是一个编译单元Jarred想拆成约100个crate加快编译速度但要避免循环依赖同时尽量减少与原始Zig实现的差异。他跑了一个工作流来分类循环依赖代码应该放哪里再跑一个工作流执行重构。修复循环依赖暴露了约16000个编译器错误。对一个人来说是个巨大数字但对64个并行的Claude来说不算疯狂。Claude有个坏习惯遇到编译错误就存根函数或者加冗长的解释性注释来证明变通方案是合理的。Jarred加了一条对抗性审查规则如果需要段落长的注释来解释为什么变通方案没问题代码就是错的修复代码。cargo check通过后目标是让bun test能跑。然后循环修复CLI子命令的崩溃再循环修复测试文件失败。测试套件里有内存泄漏测试、集成测试、压力测试有些测试跑一分钟以上有些耗尽TCP套接字有些读写GB级数据到磁盘有些生成约一万个进程。从第一个CI运行到Linux全绿失败文件从972降到23用了一天半。Windows最后完成。最终全部6个平台全绿。移植过程中的经典错误debug_assert宏在发布构建中被完全擦除包括函数调用。Zig的assert是函数参数在任何构建中都运行。Rust的debug_assert是宏发布构建中整个表达式被擦除。这导致热模块替换在特定React项目中失效。Rust的bytemuck::cast_slice对奇数长度切片会panic。Zig的reinterpretSlice使用截断除法忽略多余字节。UTF-16 BOM加奇数个字节的场景导致进程崩溃。Zig的ReleaseFast构建移除边界检查Rust的发布构建保留。模块解析器里有个溢出块大小从2048降到了64把850万内联文件名的上限降到了27万真实项目触发了越界Rust正常panicZig在ReleaseSafe下也会panic但Bun只在Windows上用ReleaseSafe。Zig的comptime格式字符串在参数替换之前就已经处理了颜色标记。Rust函数没有comptime参数Output::pretty只看到最终字符串在参数上也重写了标记。bun update -i打印的包名超链接被破坏。修复方案是把它变成宏。Rust版Bun的实际改进v1.4.0修复了128个在v1.3.14上可复现的bug从内存泄漏到崩溃再到颜色错误的帮助文本。Rust的Drop trait自动清理内存。在Zig里defer需要在每个调用点手动添加。容易忘记清理导致内存泄漏或者在错误处理路径中执行两次清理导致双重释放。Rust的Drop在值不再可访问时自动运行用没有隐式控制流换来了防止常见陷阱的能力。Bun.build在循环中调用2000次v1.3.14里每次泄漏约3MB总共6.7GB。v1.4.0里内存稳定在609MB。之前在Zig里尝试修复这个问题没有合并因为缺少Drop等效机制让团队难以建立信心。二进制体积缩小了。初始Rust重写让Windows版减少3.8MBmacOS减少5.5MBLinux减少6.8MB。加上ICU优化和链接器折叠总体缩小约20%。Windows从94MB降到76MBLinux从88MB降到70MB。Rust的LLVM IR会为栈变量生成llvm.lifetime.start和llvm.lifetime.end让LLVM复用栈空间槽位。Toml解析器和其他递归下降解析器使用更少栈空间。之前团队手动把特别大的函数拆分成小函数来绕过Zig的栈空间问题。Rust支持C/C和Rust之间的跨语言链接时优化可以在编程语言之间内联。HTTP吞吐量提升2.8%到4.8%next build提升4.5%tsc -b提升4.7%。生产环境验证和后续工作Prisma Compute公测版跑在Rust重写的Bun上。之前的Zig版Bun有内存泄漏和连接池在VM暂停恢复后无法恢复的问题。Rust版完美处理了这些故障模式。Claude Code v2.1.181开始使用Rust版BunLinux启动速度提升10%几乎没人注意到。无趣就是好事。合并Rust重写后团队完成了11轮Claude Code Security安全审查。添加了24/7覆盖引导的模糊测试覆盖JavaScript、TypeScript、JSX、CSS、JSON5、JSONC、TOML、YAML、Markdown、INI、Bun Shell脚本、semver范围、patch文件和CSS颜色。模糊器自动把发现的bug发给Claude提交PR人类审查PR。目前解析器被执行了1000亿次产生了大约15个PR。约4%的Rust代码在unsafe块里约13000个unsafe关键字分布在约27000行里占78万行代码的4%。其中78%的unsafe块只有一行指向来自C的指针或调用C库。这个比例会随着从忠实Zig移植向惯用Rust重构而下降但因为要继续使用JavaScriptCore这些C/C库unsafe会一直比纯Rust项目多。Jarred算了一笔账预合并阶段消耗了59亿未缓存输入token6.9亿输出token720亿缓存输入token读取。按API定价约16.5万美元。人工完成这项工作需要3个对代码库有完整上下文理解的工程师干一年期间无法改善Node.js兼容性、修复bug和漏洞、实现新功能。Bun团队永远不会做这种重写。现实的替代方案是什么都不做继续修复文章开头那些bug。这件事到底意味着什么一个工程师带着Claude Code的对抗性审查工作流11天完成了3个工程师一年的工作。内存泄漏归零体积缩小20%Jarred的角色从写代码变成了设计工作流、监控输出、发现问题时修改流程而不是手工修复代码。6502次提交每次提交都被至少两个对抗性审查员检查过。1695次提交每小时峰值695次提交在一个小时内完成。这些数字背后不是自动化的胜利而是一种新的工作方式。Zig让Bun从0到1成为可能。Rust让Bun从1到100变得更稳定。编程语言的选择曾经是一个单向门现在有了LLM辅助重写这个门在某种程度上变成了双向的。但Jarred最核心的观点是一个人能做的事情比一年前多太多了。这可能是整个故事最值得记住的结论。一句话总结Bun用11天从Zig跳到Rust16万美元API费用换3个工程师一年的工作量稳定性和性能双双提升这也许就是软件工程的新常态。原文https://www.jdon.com/93246-rewriting-bun-in-rust-11-days-6500-commits.html