资讯中心

Bun 重写为 Rust:11天、64个Claude、16.5万美元,一次改变行业认知的激进实验

📅 2026/7/10 4:05:40
Bun 重写为 Rust:11天、64个Claude、16.5万美元,一次改变行业认知的激进实验
2026 年 5 月 14 日Bun 团队做了一件几乎所有资深工程师都会告诉你「绝对不要做」的事——将 535,496 行生产级 Zig 代码重写为 Rust。更疯狂的是整个过程只用了 11 天由一个人主导、64 个 Claude 代理并行执行总 API 成本约 16.5 万美元。结果呢零测试被删除全部 60,000 测试通过二进制体积缩小 20%内存泄漏基本清零吞吐量提升 2-5%修复了 128 个历史 bug。这不是一个关于「Rust 比 Zig 好」的故事。这是一个关于「当 AI 足够强大时什么才是可能的」的故事。起点一个靠 Zig 起飞的疯狂项目把时间拨回 2021 年。Jarred Sumner 在 Oakland 一间逼仄的公寓里在单人、无 LLM 辅助的条件下用 Zig 语言写出了 Bun 的第一个版本。他说得很直白「对一个野心过大、单人开发的项目来说默认结局就是成为 GitHub 个人主页上那些死掉的 side project 坟墓里的一员。Zig 让 Bun 成为可能。」他赌对了。如今 Bun 每月的 CLI 下载量超过 2200 万次Claude Code、OpenCode 等工具依赖它作为运行时Vercel、Railway、DigitalOcean 都提供了原生支持。但成长的另一端是稳定的代价。二、稳定性的黑洞当手动内存管理遇上 GCBun 的定位注定了它的复杂性JavaScript/TypeScript/CSS 的转译、压缩、打包npm 兼容包管理器Jest 兼容测试运行器Node.js API 实现fs、net、tls 等HTTP/1.1 和 WebSocket 客户端模块解析器问题的核心在于JavaScript 是一门垃圾回收语言而 Zig像 C 一样不管理你的内存。Jarred 在文章中贴出了一份触目惊心的 bug 清单——来自 v1.3.14 一个版本的修复记录node:zlib中调用.reset()时与异步.write()竞争导致 heap-use-after-freenode:http2中重入 JS 回调触发哈希表 rehash 导致内部流指针失效UDPSocket.send()中valueOf()/toString()回调在 payload 捕获和实际发送之间分离 ArrayBuffertlsSocket.setSession()每次调用泄漏一个 SSL_SESSION约 6.5 KBfs.watch()在.close()后 watcher 从未被 GC 回收CSS 解析器中background-clip多 vendor prefix 多层背景导致 double-free这不是孤例。这是混合 GC 手动内存管理架构的系统性后果。Zig 的「美德」也是它的「缺陷」Zig 设计的核心理念之一是「没有隐藏控制流」。它用defer和errdefer来显式地清理资源而不是像 C 的析构函数或 Rust 的Drop那样在作用域结束时自动运行。语言清理机制Zigdefer、errdeferC析构函数、移动语义RustDroptrait问题是当你把一个*T传递给多个函数时你怎么知道它什么时候不再被引用什么时候可以安全释放Bun 的应对方案是混合使用 arena 生命周期、引用计数以及一条最致命的原则——「非常仔细地审查」。对 53 万行代码来说「非常仔细」不是一句可以兑现的承诺。二、「保持理智、不要犯错」本身就是一个 bug 模式Bun 团队当时已经在做着行业内顶级的稳定性工作给 Zig 编译器打补丁加入 Address Sanitizer 支持每次提交都跑 ASANWindows 上使用 Zig 的 ReleaseSafe 编译保留安全检查使用 Fuzzilli 对 JavaScript 引擎做 24/7 持续模糊测试大量的端到端内存泄漏测试这已经比大多数项目做得多了。但 bug 仍然在出现。利用户的话来说——“我不想每天晚上睡前都在担心 Bun 的 crash。”引入智能指针、制定代码风格规范——这些都是可选项。TigerBeetle 有 TigerStyleGoogle 有 31,000 字的 C 风格指南。但风格指南靠 code review 执行而 code review 靠人人靠不住。Jarred 也说出了自己的顾虑在 Zig 里写智能指针失去了 Rust 的编译器保证却承担了所有复杂度。三、为什么不选 CBun 已经嵌入了大量 C/C 库JavaScriptCore、uWebSockets、BoringSSL、SQLite。大约 20% 的代码本身就是 C。C 能带来构造/析构函数能删除大量extern C胶水代码。但 Jarred 的判断是C 仍然依赖风格指南和人工审查仍然会让内存问题漏网。编译器错误是最好的反馈回路。这是 Rust 的核心论点。四、那个疯狂的 11 天这里的关键问题不是「Rust 重写好不好」而是「AI 能不能帮我们做这件事」。Jarred 的原计划是用 Rust 风格智能指针在 Zig 里修稳定性——但说实话他不想干。于是产生了一个念头「我能不能花一周时间测试 Anthropic 的新模型能不能把 Bun 重写成 Rust」他最初也不认为会成功。几天后一个高比例的测试套件开始通过了。准备工作3 小时的对话Jarred 花了大约 3 小时和 Claude 讨论如何把 Zig 模式映射到 Rust。这次对话被序列化为一份PORTING.md后来还上了 Hacker News。下一个问题如何把手动管理的内存的代码映射成 Rust 的生命周期他的做法是写一个「动态工作流」让 Claude阅读代码库中每个结构体的每个字段追踪控制流对每个复杂生命周期的字段提出生命周期的建议使用 2 个 adversarial review 代理审核建议应用反馈后序列化到LIFETIMES.tsv文件中供其他 Claude 参考然后再做一轮 adversarial review交叉检查PORTING.md和LIFETIMES.tsv。他本人也手动通读了一遍。试运行 各种翻车在要求 Claude 翻译所有 1,448 个.zig文件之前先试了 3 个文件。每个文件的工作流是1 个实现者写.rs2 个 adversarial reviewer 检查1 个 fixer 应用修改。然后就是各种翻车有两个 Claude 互相踩——一个跑git stash另一个跑git stash pop另一个运行git reset HEAD --hard如果放进独立 worktree磁盘空间不够Jarred 的解决方式修改工作流指令禁止任何不是「一次性提交单个文件」的 git 命令。然后分成 4 个 worktree每个 worktree 跑 16 个 Claude。写代码的峰值每分钟 1300 行在并行化的巅峰时Claude 每分钟写了约 1300 行代码。每一行代码都经过 2 个 adversarial reviewer 审核 1 个 fixer 修改后方才提交。结果没有一行能跑。这是预期行为。他还发现了一个硬件问题——EC2 实例的默认 IOPS 不够。一个grep命令就能冻结磁盘读写好几分钟。编译器错误作为工作队列写完所有代码后下一个工作流是 crate 级别的cargo check错误修复。最大的坑是循环依赖。Zig 代码库是单一编译单元相当于一个 crate而 Rust 需要拆成约 100 个 crate。Jarred 之前尝试预先处理这个问题的 PR 不够用于是又跑了一个工作流来分类循环依赖的归属。这轮揭示了约 16,000 个编译错误。对人类来说天文数字对 64 个 Claude 来说不是。他的工作流模式对每个 crate 跑cargo check按文件分组保存错误修复该 crate 内所有编译器错误2 个 adversarial reviewer 审核1 个 fixer 应用修复又翻了一次车Claude 把「让所有 crate 通过编译」理解成了「stub 掉有编译错误的函数」还开始加很长的注释来合理化 workaround。Jarred 的反制在 adversarial reviewer 的规则里加了一条——「如果你需要写一段话来说明为什么 workaround 是可以接受的那代码就是错的——修好它。」从 smoke test 到全部通过通过编译后链接错误、panic、各个 CLI 子命令逐步修复。然后跑测试。先本地再做 CI。这个阶段拿出了更强的隔离手段systemd-runcgroups限制内存和 CPU、隔离 PID 命名空间。机器还是爆了几次磁盘。两天后Linux CI 全绿。从 972 个失败测试文件降到 0。最终数据指标值时间11 天5 月 3 日 → 5 月 14 日提交次数6,778峰值并行数64 个 Claude输入 tokens59 亿未缓存输出 tokens6.9 亿缓存读取720 亿API 成本约 16.5 万美元删除的测试0人工等效工作量3 个全上下文工程师 × 1 年「手工做这件事3 个完全了解代码库的工程师大概需要 1 年。在此期间我们不能修 bug、不能加功能、不能做安全修复。这根本不是一个可行的选项。现实的选择是什么也不做永远修文章开头那些 bug。」五、迁移到 Rust 之后的真实收益5.1 内存使用大幅下降最核心的原因——Drop。Zig 的defer需要在每个调用点显式写清理代码一个遗漏就是泄漏错误路径上写两次就是 double-free。Rust 的Drop在值离开作用域时自动运行。效果最明显的例子Bun.build()内存在循环调用下不再泄漏。// 在同一个进程内反复构建同一个项目 2000 次for(leti0;i2000;i){awaitBun.build({entrypoints:[./index.js],minify:true,sourcemap:external,});}构建次数v1.3.14 (Zig)v1.4.0 (Rust)5001,914 MB526 MB1,0003,506 MB586 MB1,5005,097 MB608 MB2,0006,745 MB609 MB每次构建泄漏约 3MB 的问题被系统性解决。之前用 Zig 做同样的事PR #24741没有合并就是因为没有等价于 Drop 的机制让人无法有足够的信心。5.2 二进制体积缩小 20%Windows94 MB → 76 MB-19%Linux88 MB → 70 MB-20%部分原因Zig 的comptime用得太多了Rust 的表现更经济。加上 ICU 数据优化、Identical Code Folding 等链接器优化12-18 MB 就这么省下来了。5.3 性能提升 2-5%Rust 支持 C/C 与 Rust 之间的跨语言链接时优化LTO经过编译计算的删除可以实现跨编程语言的内联。框架v1.3.14v1.4.0提升Bun.serve169.6k req/s177.7k req/s4.8%node:http103.8k108.5k4.5%Elysia158.9k163.3k2.8%Express64.5k66.6k3.2%Fastify91.5k95.9k4.8%next build13.62s13.03s4.5%tsc -b --force0.94s0.89s4.7%5.4 修复了 128 个历史 bugv1.4.0 相较于 v1.3.14 修复了 128 个 bug覆盖内存泄漏、crash、甚至帮助文本颜色错误。六、重写的代价19 个回归这次迁移不可避免地引入了 19 个已知回归每个都已修复。Jarred 坦然承认了这些错误也分享了最有启发性的几个教训debug_assert!中的副作用Zig 的assert是一个函数——参数在所有构建中都会执行。Rust 的debug_assert!是宏——release 构建中整个表达式被擦除。有个insert_stale调用包装在debug_assert!里release 构建下就不再运行了。结果 React Hot Module Replacement 在某些情况下无声地坏了。Debug 构建正常工作release 构建崩溃。奇数长度字节切片的差异Zig 的reinterpretSlice(u16, bytes)使用divTrunc静默忽略最后一个奇数字节。bytemuck::cast_slice直接 panic。Blob.text()在 UTF-16 BOM 后跟奇数个字节时 panic 了整个进程。修复方案很务实回到忽略奇数位——buf[..buf.len() !1]。Bounds checks 的差异macOS/Linux 上 Zig 用ReleaseFast模式编译去除边界检查Rust 的 release 构建保留边界检查。模块解析器把长文件名内联到一个全局列表中溢出到 overflow blocks。Zig 的原始代码把每个 block 大小设为 270,272一个占位符值比原意低 30 倍。真实项目触顶了。Rust 用 panic 代替写出界——这里是安全的信号。comptime与宏的区别Zig 的comptime参数可以让格式化参数在字符串解析之前就替换好。Rust 没有comptime导致Output::pretty在 ANSI 转义码解析时吞掉了参数。结果是bun update -i的 OSC 8 超链接尾部\紧挨着r标记标记解析器吃掉了转义符打印出 “oxfmtr” 而不是 “oxfmt”。修复方案bun_core::pretty!宏。这些回归案例的高价值在于它们不是「写错了」而是两种语言的语义差异——语法上看似相同语义上截然不同。这在 AI 驱动的跨语言迁移中是最高发的错误类别。七、Prisma 的背书与 Claude Code 的无声切换Prisma 在 Rust 重写版本上启动了 Prisma Compute 公测。他们的技术负责人 Alexey Orlenko 的原话「我们遇到了内存泄漏和 VM 暂停/恢复后连接池无法恢复的问题。Rust 重写版本处理后它完美处理了这些故障模式。」Claude Code v2.1.181 是第一个使用 Rust 版 Bun 的发布版本。从生产遥测数据来看Linux p50 启动时间从 517ms 降到 464ms——10% 的加速。「几乎没人注意到这件事。无聊就是好东西。」八、更大的图景AI 时代的软件重写范式我们需要退后一步看这件事的真正意义。不是「Rust 赢了、Zig 输了」Jarred 反复强调Zig 让 Bun 成为可能。「如果没有 Zig我们不可能有今天。」这不是语言之争。这是一个基于所有权和生命周期进行系统性资源管理的软件在混合 GC/手动内存模型中的结构性缺陷。C 在理论上可以做到 Rust 能做的事。Google、Microsoft 能用风格指南勉强管住。但问题的本质不是「这个语言能不能」而是编译器能不能在写代码时告诉我做错了。风格指南靠人执行。编译器错误不靠人。AI 把「不可能」变成了「一个工程问题」这是我认为这篇博文最重要的段落「如果手工重写3 个全上下文工程师大概需要 1 年。那意味着 1 年不能修 bug、不能做安全更新、不能加功能。现实的选择是要么不重写要么永远修文章开头那些 bug。」AI 没有消除重写的风险——它把「风险 x 工作量」的乘积降低了两个数量级。£16.5 万美元的 API 费用、11 天的工期、64 个并行 Claude——这些东西加起来对于大多数公司来说是一个可以接受的工程预算而不是一个不可能的任务。「Claude Code 动态工作流」模式Jarred 这个项目的核心架构不是 Rust而是Claude Code 的动态工作流Dynamic Workflows——把跨 64 个 Claude 实例的协作组织成一个循环while (task todoList.pop()) { result task() feedback await Promise.all([review(result), review(result)]) await apply(feedback, result) }关键洞察拆开实现者和审查者——写代码的那个 Claude 不审查审查的那个不写代码每个任务配 2 个 adversarial reviewer——他们的唯一工作是「找到代码不work的理由」修复的是流程不是代码——当一个模式出现问题比如长注释合理化 workaround他修改的是工作流规则而不是手动修复每个实例这是AI 时代的 TDD不是测试驱动开发而是Review-Driven Development——审查驱动开发。八、Bun 的未来Bun v1.4.0 是第一个 Rust 版本。目前约 4% 的代码在unsafe块内约 13,000 个unsafe关键字其中 78% 是单行——来自 C 的指针或调用 C 库。这个比例会随时间下降。Bun 仍然嵌入了 JavaScriptCore仍然需要 C/C 接口。它不会变成一个「纯 Rust 项目」。但那部分用 unsafe 封装的边界比内部的 Zig 代码更容易审计。团队已经在运行的保障体系Rust 的 borrow checkerCI 中完整运行Miri在 CI 中对不断增长的部分代码运行LeakSanitizer 集成追踪所有原生内存分配24/7 coverage-guided 模糊测试覆盖所有解析器JS、TSX、CSS、JSON5、TOML、YAML、Markdown、INI、Bun Shell 脚本、semver 范围、.patch 文件、CSS 颜色等自动提交 PRfuzzer 发现的 bug 自动送给 Claude 生成修复 PR「一个工程师在一年前能做的事比今天少得多。」结语Bun 的 Rust 重写是一个信号。不是「Zig 不够好」——Zig 仍然是做系统级编程最有趣的语言之一。不是「Rust 适合所有事」——Rust 的学习曲线不会凭空消失。不是「AI 马上要取代程序员了」——这个项目有一个顶级工程师紧盯每一个步骤。这个信号是软件工程中那些因为「代价太高」而被搁置的决策现在需要重新审视。过去重写一个 53 万行代码的生产项目是不现实的。过去跨语言迁移意味着冻结功能开发一年。过去在混合内存模型的项目里修内存泄漏更像哲学追求而不是工程目标。这些「过去」正在快速变成「过去式」。原文Rewriting Bun in Rust — Jarred Sumner Bun / Anthropic原创技术博客 · 开源项目架构深潜 · idao.fun