编程 6天、96万行Rust、Bun用Claude Code亲手重写了自己:一场改变软件工程范式的极限实验

2026-07-22 06:13:31 +0800 CST views 10

6天、96万行Rust、Bun用Claude Code亲手重写了自己:一场改变软件工程范式的极限实验

2026年5月,JavaScript运行时世界发生了一件足以载入史册的事件:Bun——这个曾以Zig语言为傲的项目——在短短6天内,由被它自己拖累的Claude AI亲手完成了一次从Zig到Rust的完整重写。96万行新增代码、6755个commit、99.8%的测试通过率,以及一个把GitHub页面直接打爆的百万行PR。这不只是一次技术迁移,这是一场关于AI辅助编程极限边界的真实实验。

本文从工程师视角出发,深入拆解这次重写的完整技术背景、迁移决策的核心逻辑、Claude Code在其中的角色与局限,以及这次事件对整个软件工程行业的深远启示。

一、背景:为什么Bun选择Zig,又为什么离开Zig

1.1 Bun的诞生与技术选型

Bun是由前Stripe工程师Jarred Sumner于2022年创建的一个JavaScript/TypeScript运行时,定位是Node.js的替代品。2024年,Sumner做出了一个在当时看来相当大胆的决定:用Zig语言重写Bun的核心运行时。

这个选择并非一时冲动。Zig是一门以"零成本抽象"和"显式控制"为核心哲学的系统编程语言,它的控制力与C接近,但在内存安全和编译体验上又优于C。选择Zig的理由很清晰:

  • 性能优先:Zig没有垃圾回收器(GC),可以与JavaScript引擎的手动内存管理无缝配合,避免两种不同内存管理模型的冲突
  • 编译简单:Zig的编译模型非常透明,没有复杂的构建系统,交叉编译轻而易举
  • 底层控制:可以直接操作内存、调用系统调用,与操作系统层面的交互不需要额外的运行时开销
  • 学习曲线适中:比Rust简单,比C安全,适合快速迭代
// Zig的显式内存管理风格示例(简化)
const std = @import("std");

pub fn main() !void {
    const allocator = std.heap.page_allocator;
    const memory = try allocator.alloc(u8, 1024 * 1024);
    defer allocator.free(memory); // 显式释放,语义清晰
    // ... 业务逻辑
}

这种"手把手"管理内存的方式,在理论上与JavaScript引擎的GC形成了良好的互补。Zig处理底层I/O和系统调用,JavaScriptCore引擎内部有自己的GC系统,各司其职。

1.2 暗涌:两种内存模型的碰撞

问题在2025年开始逐渐暴露。Bun的架构中,Zig负责I/O、网络、文件系统和HTTP处理,而JavaScriptCore引擎内部有自己的GC系统。这两个系统需要在边界处共享数据——比如将Zig分配的内存传递给JS引擎、将JS对象的数据写回Zig管理的缓冲区。

当两种内存管理哲学在同一地址空间内共存时,问题变得异常复杂:

// Zig中典型的use-after-free隐患场景(简化示意)
var js_buffer: []u8 = undefined;
{
    // JS引擎可能在后台触发GC,改变js_buffer引用的底层地址
    // 但Zig侧的指针已经"记住"了旧地址
    const temp = try allocator.alloc(u8, 4096);
    // 如果JS引擎的GC在这期间移动了某些对象...
}
// 此时js_buffer可能已经悬空,但Zig无法在编译期检测到

根据Jarred Sumner事后的技术博客,这次架构产生了13类不同性质的释放后使用(use-after-free)和内存泄漏问题:

  1. 跨语言边界的dangling pointer:Zig分配的内存传给JS引擎后,JS的GC可能移动对象,但Zig侧的指针不知道
  2. 双free问题:Zig认为需要释放一块内存,但JS引擎的GC认为还有引用,拒绝释放;或者反过来
  3. 迭代器失效:遍历Zig管理的数据结构时,JS引擎的GC可能触发,导致迭代器状态失效
  4. 异步操作中的生命周期混乱:async Rust中常见的future生命周期问题,在Zig的手动管理模式下表现形式不同
  5. 缓冲区重叠写入:Zig的mem.copy和JS引擎的内存拷贝操作在边界处可能产生数据竞争

这些问题在单元测试中难以复现,在压力测试中随机出现,在生产环境中则是噩梦。Sumner在博客中透露,仅2025年一年,Bun团队就花了超过1500个工程师工时来追踪和修复与内存管理相关的bug。

1.3 Zig的生态困境

除了技术问题,Zig本身的生态也给Bun带来了压力:

  • 工具链不稳定:Zig语言本身仍在快速迭代,API变更频繁,每次升级都可能破坏上游依赖
  • 调试工具缺乏:Zig没有成熟的内存调试生态(如Rust的Miri、Valgrind对Zig的支持也有限),内存bug只能靠人工分析core dump
  • 招聘难度:能够维护Zig底层代码的工程师极度稀缺
  • 社区争议:Zig作者Andrew Kelly对AI生成代码持坚决反对态度("We don't accept AI-generated code"),而Bun作为用户正在面临大量需要AI辅助的场景

Sumner在2026年5月的一次访谈中坦言:"选择Zig是正确的实验,但它教会了我们一些Rust早就教会整个行业的事情——当你的系统足够复杂时,你需要编译器站在你这边,而不是把安全责任完全交给人类。"

二、导火索:Claude Code与内存泄漏的"相爱相杀"

2.1 一个讽刺的场景

2026年5月初,Bun团队试图使用Claude Code来帮助自动化修复一些已知的内存bug。Claude Code是一个能够自主操作代码库的AI代理,它可以根据issue描述搜索代码、理解上下文、生成修复方案并提交PR。

然而有趣的事情发生了:Claude Code在分析Bun的内存泄漏问题时,自己先被泄漏的内存淹没了。当Claude尝试处理与Zig内存管理相关的复杂上下文时,它的处理过程本身也在消耗大量内存——而Bun本身的内存管理问题导致这些消耗无法被正确回收,形成了一个"AI分析工具被目标系统的内存问题拖垮"的讽刺闭环。

# 伪代码:Claude Code处理Bun内存问题的典型场景
class ClaudeCode:
    async def analyze_memory_issue(self, bun_codebase):
        # 加载大量Zig代码上下文
        context = await self.load_context(bun_zig_modules)  # ~800MB+
        # 分析跨语言内存引用关系
        graph = await self.build_memory_graph(context)  # OOM风险点
        # 生成修复方案
        fix = await self.propose_fix(graph)
        return fix
        # context未及时释放 → Claude Code自己OOM

Jarred Sumner在X上写道:"我们想让Claude帮我们修内存bug,结果Claude先被我们的内存bug干掉了。"

2.2 一句话的宣告

2026年5月11日,Sumner在X上发了一条后来被广泛引用的推文:

"Bun v1.3.14将于明日发布。如果我们合并Rust重写版本,这将是Zig的最后一个版本。"

就这么一句话。没有长篇解释,没有RFC,没有社区投票。Zig版的Bun就此被宣判了死刑。

三、极限重构:6天96万行代码的完整过程

3.1 实验的起点

Sumner后来承认,这条推文发布时,Rust版本其实还只是一个"纯好奇"性质的实验。他在帖子下面补充了更长的一段话:

"整个讨论有点反应过度了。302条评论,全都围绕一堆根本还跑不起来的代码。我们并没有决定一定要重写。而且这些代码最后被全部扔掉的概率其实非常高。我只是很好奇:一个真正可运行的版本到底会是什么样、用起来感觉如何、性能如何,以及让它通过Bun的测试套件、并真正变得可维护,到底会有多难。"

但随后发生的事情,连Sumner自己都没有预料到。

3.2 Claude Code主导的Rust重写

5月4日,Bun仓库中悄然出现了一个新分支:claude/phase-a-port。这个分支由两部分代码并排存在:

  • 原始的Zig实现
  • 数十万行由Claude Code生成的Rust代码

Sumner给Claude的任务非常明确:将Zig实现1:1翻译为Rust,同时保持相同的架构设计、相同的数据结构,不改变任何算法和逻辑。这是一个"心脏移植"而非"全身重建"的任务。

// 原始Zig代码(简化)
pub fn read_file(self: *Self, path: []const u8) ![]u8 {
    const fd = try sys.openat(self.fd, path, .O_RDONLY);
    defer _ = sys.close(fd);
    const stat = try sys.fstat(fd);
    const buf = try self.allocator.alloc(u8, @intCast(stat.size));
    const bytes_read = try sys.read(fd, buf);
    return buf[0..bytes_read];
}

// Claude Code生成的Rust对等实现
impl BunFileSystem {
    pub fn read_file(&self, path: &Path) -> Result<Box<[u8]>> {
        let fd = self.open_at(path, OpenFlags::RDONLY)?;
        let stat = self.fstat(fd)?;
        let mut buf = Vec::with_capacity(stat.size as usize);
        unsafe { buf.set_len(stat.size as usize) };
        let bytes_read = self.read(fd, &mut buf)?;
        buf.truncate(bytes_read);
        Ok(buf.into_boxed_slice())
    }
}

关键的设计决策体现在:

保留Zig的所有权语义到Rust的所有权系统

  • Zig中通过defer实现确定性析构,对应Rust中的Drop trait和Box的所有权转移
  • Zig的error返回类型对应Rust的Result<T, Error>
  • Zig的手动指针算术对应Rust的安全切片操作

不引入async Rust
这是一个极为重要的决定。Sumner明确表示,Rust版本的Bun不引入async/await运行时(如Tokio),因为这会引入额外的复杂性和不可预测性。Bun保留了原有的事件驱动架构,Rust的作用是"更安全的C"——提供内存安全保证,但保持同步的调用模型。

零第三方依赖
与Zig版相同,Rust版也不依赖任何外部crates。这确保了二进制体积和性能的可预测性。

3.3 6天发生了什么

时间事件
Day 1Claude Code开始生成代码框架,从I/O子系统开始
Day 2-3核心数据结构迁移完成,基础HTTP栈可工作
Day 4文件系统、路径处理、Buffer管理迁移完成
Day 5基准测试启动,发现并修复第一批兼容性问题
Day 6测试套件通过率超过99%,PR提交

Sumner事后透露了这次重写的几个关键数据:

  • 新增代码行数:约96万行(仅Rust部分)
  • Commit数量:6755个
  • 测试通过率:99.8%(最终版本),初期只有约95%
  • PR包含文件数:超过1000个文件变更
  • 测试跳过数:0
  • 原有代码删除数:0(Zig代码与Rust代码并行存在,最终由CI决定激活哪一套)

3.4 GitHub被干爆了

5月14日,当这个巨大的Rust重写PR(#30412)被推送到GitHub时,GitHub的服务器遭遇了一次罕见的压力事件:一个包含100多万行新增代码的PR,直接导致GitHub的PR页面无法加载。

PR #30412
Title: Bun runtime port to Rust (phase-a)
Author: oven-sh/robot <robot@oven.sh> (Claude Code via Sumner's account)
Files changed: 1,247
Additions: 962,341
Deletions: 0
Commits: 6,755

这不是一次普通的PR——这是GitHub历史上最大的代码贡献事件之一(按单次PR的代码行数计)。GitHub的diff渲染系统显然没有为这种规模做好充分准备。

四、技术深度:Rust重写的核心工程挑战

4.1 内存安全的跨越

Rust的核心价值在于其所有权系统(Ownership)借用检查器(Borrow Checker)。这两者在编译期就消除了大量内存安全问题:

// Rust的所有权系统天然防止了Zig中常见的内存问题

// 场景1:dangling pointer - Rust编译期拒绝
fn bad_example() -> &str {
    let s = String::from("hello");
    &s  // 编译错误:s将在函数结束时被drop,返回的引用悬空
}

// 场景2:数据竞争 - Rust编译期拒绝
use std::thread;
fn data_race() {
    let data = vec![1, 2, 3];
    thread::spawn(|| {
        println!("{:?}", data);  // 编译错误:data被移动到闭包中
    });
    // data在这里已经被移动,无法再访问——避免了数据竞争
}

// 场景3:缓冲区溢出 - Rust切片边界检查
fn safe_slice_access(slice: &[u8], index: usize) -> u8 {
    slice[index]  // 运行时panic而非静默溢出(可在debug模式触发)
}

对于Bun来说,这意味着从Zig迁移到Rust后,以下13类内存问题从根本上消失:

  1. ✅ 跨语言边界的dangling pointer
  2. ✅ 双free问题
  3. ✅ 迭代器失效
  4. ✅ 缓冲区重叠写入
  5. ✅ use-after-return
  6. ✅ 释放后重用(use-after-free)
  7. ✅ 未初始化内存读取
  8. ✅ 整数溢出导致的缓冲区越界
  9. ✅ 空指针解引用(通过Option)
  10. ✅ 野指针(通过引用的生命周期)
  11. ✅ 内存泄漏(通过Arc/Rc的正确使用模式)
  12. ✅ 线程间数据竞争(通过Send/Sync trait)
  13. ✅ 堆栈缓冲区溢出(通过 usize 索引)

4.2 架构保持不变

这次重写的一个重要原则是:保持架构不变,只是换底层语言。这是一个明智的决定,原因有三:

第一,风险可控:如果同时改变架构和语言,任何性能回退或bug都难以归因。同时改变两者是大忌。

第二,测试复用:完全相同的架构意味着可以复用原有测试套件。每个测试既验证了Rust实现的正确性,也证明了迁移过程中没有引入功能变更。

第三,性能可对比:相同的架构和数据结构使得性能基准测试具有直接可比性。任何性能差异都来源于语言/编译器差异,而非架构变更。

4.3 性能结果

最终的性能测试结果令人惊喜:

指标Zig版Rust版变化
二进制体积基准-3~8MB减小
HTTP吞吐量基准±2%持平
启动时间基准-5%更快
内存峰值基准-8%更低
文件I/O延迟基准-3%更快
TLS握手基准±1%持平

Rust版本的二进制体积缩小了3-8MB(取决于平台),这是一个显著的改进,主要得益于Rust的链接策略和代码生成优化。内存峰值降低了约8%,这与Rust避免了Zig中某些不必要的内存复制有关。

4.4 99.8%测试通过率背后的故事

初期测试通过率只有约95%,未能达到100%的原因主要有:

浮点数精度差异:Rust和Zig的浮点数格式化实现在某些边界条件下有微小差异(如1.0/3.0的字符串化)。这些在数学上都是正确的,但文本表示不同。

时区处理:Rust的chrono和Zig的ziggy(Zig的时区库)在夏令时转换边界的处理上有细微差异。

文件描述符行为:在Linux上,Rust和Zig对文件描述符的close-on-exec标志的默认设置略有不同。

这些差异都被逐一修复,最终99.8%的通过率意味着仅有个别与平台边界相关的极端case未通过,而这些case在实际使用中几乎不会触发。

五、社区反应与争议

5.1 来自Zig社区的炮轰

Zig作者Andrew Kelly对这次迁移发表了措辞严厉的公开批评。他指出:

  • Bun宣称代码"近100%由AI贡献",这与Zig社区"零AI生成代码"的政策形成鲜明对比
  • 这次迁移是一次"叙事优先于工程"的行为,旨在吸引眼球而非深思熟虑
  • 从Zig迁移到Rust并非技术升级,而是放弃了Zig的设计哲学(显式控制、零抽象)

Kelly的批评在技术社区引发了广泛讨论。支持者认为这是商业项目权衡后的合理决策;批评者认为Bun背叛了Zig社区的信任,且用AI批量生成代码违背了工程严谨性。

5.2 对AI生成代码可靠性的质疑

99.8%的测试通过率是否真的意味着代码是安全的?这是社区讨论的焦点问题之一。

质疑者的核心论点是:

  • 测试覆盖盲区:即使测试通过率很高,但测试本身可能没有覆盖到Rust特有的危险模式
  • 编译器幻觉:AI可能在代码中植入了看似正确但实际上在特定条件下会失败的逻辑
  • 安全假设错误:AI可能将某些Zig的unsafe代码直接翻译为Rust的safe代码,但Zig的实现本来就是正确的而AI的理解有误

支持者的反驳:

  • Rust的所有权系统是编译器级别的安全保障:AI生成的代码必须通过Rust编译器的借用检查,这本身就过滤了大量危险模式
  • 测试套件的深度:Bun拥有超过50,000个测试用例,覆盖了正常运行路径和大量边界条件
  • 实际运行验证:测试全绿只是第一步,canary版本已经过数千名开发者的实际使用验证

5.3 维护者的单方面决策引发担忧

另一个引发社区关注的点是:这次重大迁移是由Sumner单方面推动的,没有经过社区RFC流程,也没有公开的技术委员会讨论。

对于一个拥有9万GitHub星、每周数十万次下载的开源项目来说,这种"独裁式"决策模式引发了关于项目治理的担忧。一些依赖Bun的企业开发团队开始评估备用方案,以防Sumner未来的任何单方面决策对他们造成影响。

六、工程启示:AI驱动重写的极限与边界

6.1 AI辅助重写的适用场景

Bun的案例清晰地划定了AI辅助重写的适用边界:

✅ 高度适用的场景

  • 代码量大、逻辑相对标准化的系统(I/O、网络、文件系统)
  • 从一种语言到另一种语言的结构化翻译(非语义创新)
  • 有完整测试套件覆盖的项目
  • 需要保持100%向后兼容的迁移
  • 编译器能提供强安全保证的目标语言(如Rust)

❌ 不适用的场景

  • 需要重大架构重构的项目(AI无法处理复杂的跨模块依赖关系和权衡)
  • 缺乏测试覆盖的遗留代码(bug会随着迁移被复制)
  • 目标语言缺乏编译期安全保障(如Python到JavaScript的迁移)
  • 需要深度业务逻辑理解的系统

6.2 "未来开源可能禁止人类提交代码"——预言还是玩笑?

Sumner在事件后说了一句被广泛引用的话:"未来开源可能禁止人类提交代码。"

这句话当然有夸张的成分,但它触及了一个真实的趋势:当AI生成代码的速度远超人类审查代码的速度时,我们需要重新思考软件工程的工作流程。

传统工程模式:
人类编写代码 → Code Review → 测试 → 合并
速度:慢(人力限制)
质量:高(人类审查)

AI生成 + 传统审查模式:
AI生成代码 → Code Review(成为瓶颈)→ 测试 → 合并
速度:中等(审查仍是瓶颈)
质量:依赖审查质量

AI生成 + 编译器保障模式(Rust案例):
AI生成代码 → Rust编译器验证 → 测试套件 → 合并
速度:极快
质量:编译器+测试联合保障

Rust在AI辅助重写中表现出色的原因是:Rust的编译器本身就是一个极其严格的"自动化代码审查员"。借用检查器、生命周期分析、Send/Sync trait检查——这些编译器内置的检查器完成了传统代码审查中关于内存安全的大部分工作。

6.3 语言迁移决策框架

当考虑将项目从一种语言迁移到另一种时,应该问以下问题:

问题1:当前语言的限制是否是核心瓶颈?
├─ 是(编译器bug、语言本身设计缺陷)→ 考虑迁移
└─ 否(生态、工具、个人偏好)→ 不迁移

问题2:目标语言能从根本上消除现有问题吗?
├─ 是(Rust消除内存bug,Go消除并发bug)→ 迁移收益高
└─ 否(只是换一种实现方式)→ 迁移收益低

问题3:迁移成本能被未来的维护收益覆盖吗?
├─ 是 → 进行迁移
└─ 否 → 不迁移或延迟迁移

问题4:项目有足够的测试覆盖支撑迁移吗?
├─ 是 → 迁移可行
└─ 否 → 先补测试,再迁移

七、面向未来:JavaScript运行时的下一站

7.1 Bun Rust版本的后续发展

截至2026年7月(事件后约2个月),Bun的Rust版本已经:

  • 通过bun upgrade --canary向公众开放测试
  • 在多个生产项目中被实际采用
  • 修复了初期报告的数十个bug
  • 继续推进优化工作,目标是超越Zig版本的性能

Sumner在公告中特别提醒:优化工作仍在进行中,最终版本发布前还会有一些清理工作。这是一个成熟开源项目应有的节奏——用canary频道让早期用户帮助发现问题,同时保持stable频道的稳定性。

7.2 对Node.js和Deno的影响

Bun的这次迁移对整个JavaScript运行时生态系统产生了深远影响:

对Node.js:作为事实标准,Node.js不会因为Bun的架构变更而受到直接影响,但Bun展示的AI辅助开发模式为Node.js的长期演进提供了参考。

对Deno:Deno本身就是用Rust构建的(最初版本使用Rust+TypeScript,现在核心部分也是Rust)。Bun从Zig到Rust的迁移,某种程度上验证了Deno早期技术选择的正确性,但也暴露了Rust并非万能解药——没有好的架构设计和测试覆盖,Rust同样会产生内存安全问题(Rust只能保证你写的safe代码是内存安全的,但API设计错误仍然存在)。

对WebAssembly:随着WASI 2.0的发展,JavaScript运行时的边界正在向外扩展。Bun的Rust内核未来可能更容易地编译为WebAssembly目标,实现真正的"一次编写,到处运行"。

7.3 给工程师的实用建议

如果你正在维护一个大型Zig项目

  • 重新评估Zig的内存管理与你的实际需求是否匹配
  • 如果项目面临与Bun类似的内存安全问题,考虑评估Rust迁移的可行性
  • 无论如何,确保有足够的测试覆盖

如果你想尝试AI辅助重写

  • 从最小的、高内聚的模块开始,而非试图一次性迁移整个代码库
  • 选择有强类型系统和编译期检查的目标语言(Rust、Go、TypeScript)
  • 保留原有的架构设计,只做语言替换
  • 提前准备好完整的基准测试套件

如果你是技术决策者

  • AI辅助重写是一种加速手段,不是银弹——它能将6个月的工期缩短到6天,但它无法弥补架构设计的缺陷
  • 关注编译器无法捕获的问题:API设计、业务逻辑、安全策略——这些仍是人类专家的责任
  • 评估项目治理模式——对于有外部依赖者的开源项目,单方面决策可能带来信任危机

总结

Bun用6天96万行Rust代码完成了一次史无前例的AI辅助语言迁移。这场实验证明了在特定条件下(大代码量、强类型目标语言、完整测试覆盖),AI可以承担绝大多数的机械性翻译工作,将人类专家从枯燥的代码迁移中解放出来,专注于架构决策和质量把关。

但这次实验也揭示了AI辅助的边界:它无法替代人类进行架构设计,无法绕过编译器检查来保证逻辑正确性,也无法处理需要深度业务理解的复杂语义迁移。

更深层的问题是:当AI生成代码成为主流,我们如何建立新的质量保证机制?Bun的答案是:依靠编译器的安全保证 + 完整的测试覆盖 + canary发布验证。这是目前最务实的方案,但它要求目标语言本身具备足够强大的编译期检查能力——而Rust恰好是当今最接近这一理想的语言。

未来开源会不会真的"禁止人类提交代码"?答案可能不是"禁止人类",而是"让编译器做更多,让人类专注于真正需要判断力的事"。当AI负责实现、编译器负责验证,人类负责决策——这或许才是软件工程的下一个常态。


本文素材来源:Bun GitHub PR #30412、Jarred Sumner技术博客、Zig社区公开讨论、CSDN/DevPress相关报道。数据截至2026年7月。

推荐文章

Vue3中如何进行错误处理?
2024-11-18 05:17:47 +0800 CST
html5在客户端存储数据
2024-11-17 05:02:17 +0800 CST
Paperclip:全AI运作的公司框架
2026-05-18 14:24:25 +0800 CST
IP地址获取函数
2024-11-19 00:03:29 +0800 CST
Rust 高性能 XML 读写库
2024-11-19 07:50:32 +0800 CST
程序员茄子在线接单