使用 Rust 重写 Bun 的经验
https://bun.com/blog/bun-in-rust
这篇文章表面上讲“用 Rust 重写 Bun”,真正有价值的部分其实是:怎样把一个百万行级别、风险极高的软件重写,变成一套可以被大量 AI 代理并行执行、持续审查和自动验证的工程流程。
- 先制定规则,再开始写代码
- 先小范围试验,再扩大规模
- 把实现、审查和修复分开
- 用客观反馈驱动 AI 工作
- 重复出现的问题要修工作流
- 多代理并行本质上是分布式系统
- 大规模迁移应先保证行为一致
- 测试套件是 AI 开发的基础设施
- 测试全绿也不等于绝对正确
作者首先花了大约三个小时,和 Claude 一起总结 Zig 与 Rust 之间的映射规则,形成关键文档:
PORTING.md:定义 Zig 的类型、模式和惯用写法应该怎样翻译成 Rust。
LIFETIMES.tsv:分析各个结构体字段的控制流和生命周期,为难以表达的生命周期提供统一方案。
增量重写,还是一次性整体重写?根据作者以前把 esbuild 转译器从 Go 移植到 Zig 的经验,他倾向一次性完成。增量重写会引入临时代码、桥接层和过渡状态,这些东西短中期会带来痛苦,未来也未必能删干净。
核心方法“实现—对抗审查—修复”的循环
文章最重要的工作单元不是单个 Claude,而是一个循环:
作者称之为“对抗性审查”。
实现者和审查者使用分离的上下文窗口:
- 实现者的目标是完成任务。
- 审查者的唯一目标是证明代码为什么可能是错的。
- 实现者不审查自己的工作。
- 审查者不参与实现。
作者观察到,生成代码的 Claude 会倾向于相信自己的方案已经可用;让一个全新上下文中的 Claude 扮演反方,更容易发现语义差异、遗漏和投机性绕过。
所以,多代理的价值不只是“同时多写代码”,也包括制造认知独立性。
失败没有被逐个手工处理,而是反馈到工作流
项目开始后出现了很多失败。例如,多个 Claude 共享工作目录时,有的执行 git stash,有的执行 git stash pop,还有的执行 git reset --hard,相互破坏了工作成果。
作者没有把它当成一次偶然事故,而是修改整个工作流:
- 禁止 git stash、git reset 等会影响全局状态的命令。
- 要求每次提交只包含明确的文件。
- 翻译阶段不允许运行昂贵的 cargo 命令。
- 将任务拆成四个工作流分片。
- 每个分片使用独立工作树。
- 每个工作流同时运行 16 个 Claude。
高峰期一共有约 64 个 Claude 同时运行。
另一次失败发生在修复编译错误时。Claude 把“让所有 crate 编译通过”理解成“把难以修复的函数改成空实现或桩代码”,还写了很长的注释为绕过方案辩护。
作者没有手动清理所有这类代码,而是为审查流程增加了一条规则:
修改提示和审查规则几个小时后,这类问题基本消失。
这体现了文章中一个非常强的思想:
当生成代码反复出错时,优先修复生成和审查代码的流程,而不是无休止地手工修复每一份产物。
编译器和测试套件承担了主要“验收”工作
机械翻译完成后,代码完全不能运行,并且存在约 16,000 个编译错误。
作者把编译错误直接当作结构化工作队列:
- 对一个 crate 运行 cargo check。
- 按文件对错误进行分组。
- 将错误保存下来,避免所有代理重复运行昂贵的检查。
- 让实现者修复一组错误。
- 让两个审查者检查修复。
- 再由修复者应用意见。
- 完成一个 crate 后继续下一个。
编译通过后,验证目标逐步升级:
- 能否链接。
- 能否执行 bun --version。
- 能否运行 bun test <file>。
- 各个 CLI 子命令是否工作。
- 随机测试文件是否通过。
- 本地完整测试是否通过。
- Linux、macOS、Windows CI 是否全部通过。
作者特别强调:合并前不仅要求测试通过,还人工确认了测试确实执行,没有因为跳过、删除或失效而制造“假绿”。
官方文章给出的规模是:
- 用时 11 天。
- 产生 6,778 个提交。
- 高峰期约 64 个 Claude 同时运行。
- Debian 上约 138 万次 expect() 调用。
- macOS 上约 126 万次。
- Windows 上约 101 万次。
- 没有通过删除或跳过测试来换取通过。
- API 定价估算约花费 16.5 万美元。
- 作者估计,如果由三名熟悉代码库的工程师手工完成,可能需要大约一年。
因此,这个项目真正的安全基础并不是“模型足够聪明”,而是:
一个非常庞大、跨平台、语言无关并且已经长期积累的测试套件。