编程 Bun 从 Zig 到 Rust 的 11 天重构:75万行代码、16.5万美元,与一场关于 AI 代码质量的世纪大辩论

2026-08-13 09:15:00 +0800 CST views 22

Bun 从 Zig 到 Rust 的 11 天重构:75万行代码、16.5万美元,与一场关于 AI 代码质量的世纪大辩论

前言:一条推文引发的地震

2026年8月初,整个技术社区被一条消息点燃:JavaScript 运行时 Bun 的创始人 Jarred Sumner 宣布,他用一批并行运行的 Claude 智能体,仅用 11天 就将 Bun 从 Zig 编程语言迁移到了 Rust 语言,产出超过 100万行 Rust 代码,按 API 定价计算耗资约 16.5万美元

这不是一条普通的版本升级公告。它背后是一场关于 AI 编程能力边界、软件工程哲学、编程语言价值观的深度碰撞——而 Zig 语言的创始人 Andrew Kelley 亲自下场,用一篇措辞激烈的博文称这次重构是"没人把关的烂代码"(unreviewed slop)。

作为一名程序员,我们应该如何理解这件事?本文将剥开事件表层的八卦,从技术架构、哲学分歧、AI 工程实践三个维度,做一次深度拆解。


一、背景:为什么 Bun 要从 Zig 迁移?

1.1 Bun 是什么

Bun 是一个"all-in-one"(多合一)的 JavaScript/TypeScript 工具包,包含:

  • 运行时(Runtime):可直接替换 Node.js,运行 JavaScript 和 TypeScript 代码
  • 包管理器(Package Manager):替代 npm/yarn/pnpm,安装速度极快
  • 打包工具(Bundler):替代 webpack/vite/esbuild,打包速度极快
  • 测试运行器(Test Runner):替代 jest/vitest,运行速度极快

Bun 最早于 2022 年发布,采用了与 Node.js 完全不同的技术选型:

组件Node.jsBun
JavaScript 引擎V8(Google)JavaScriptCore(Apple WebKit)
底层语言C++Zig
包管理器npm内置(Rust 实现)
打包工具webpack/vite内置(Zig 实现)

选择 JavaScriptCore(JSC)的理由是:相比 Google 的 V8,JSC 在启动速度和内存占用上更有优势——这对于需要快速启动的工具链场景非常重要。

选择 Zig 的理由是:Zig 以"无隐藏控制流、无隐藏内存分配"著称,提供与 C 相当的底层控制能力,同时语法比 C 更现代,编译时间比 Rust 更短,非常适合写系统级代码。

1.2 困境的种子:从第一天就埋下了

然而,Sumner 在事后发布的详细博文中承认了一个关键事实:

"Bun 的架构混合了垃圾回收(GC)和应用程序驱动的内存管理,而 Zig 本身就不是为这种混合模式设计的。"

这是一个非常诚实的自我剖析。Zig 的内存管理哲学是"一切显式"——程序员手动管理内存,编译器不做任何隐藏的 GC。这意味着如果你想用 Zig 实现一个同时需要 GC(用于 JavaScript 对象)和手动管理(用于底层系统调用)的混合系统,你必须自己实现两套内存管理逻辑,并在它们之间小心翼翼地做边界处理。

Bun 恰好就是这样一个混合系统:

  • JavaScript 对象由 JSC 的 GC 管理
  • 底层文件 I/O、网络、FFI 等需要手动内存管理
  • 两者之间需要频繁交互,边界处理极其复杂

随着 Bun 用户群从 0 增长到数十万,代码库膨胀到 50 万行级别,这些边界处理的 Bug 开始像打地鼠一样涌现。

1.3 被暴露的漏洞:Anthropic 代码泄露事件的蝴蝶效应

2026年3月,Anthropic 发生了严重的源代码泄露事件——约 51.2 万行代码被意外暴露。但鲜为人知的是,这次泄露的根源之一被追溯到 Bun 的 Bundler 中存在的一个漏洞:即使构建时明确禁用了源映射(source map)生成,该漏洞仍然会在构建过程中偷偷生成源映射文件,导致源代码在不知情的情况下被嵌入产物中。

据 NodeSource 报道,正是这个漏洞使得 Anthropic 的 Claude 源代码意外暴露。

这让 Sumner 意识到:Bun 的代码质量问题已经不只是"小 Bug",而是开始影响用户的安全和信任。继续在 Zig 架构上打补丁已经不是可持续的方案。

1.4 为什么选 Rust 而不是继续用 Zig?

Rust 的核心优势在这里恰好是 Zig 的劣势:

  1. 所有权系统(Ownership)和借用检查器(Borrow Checker):Rust 的编译器强制你正确处理所有内存访问,GC 和手动管理的混合场景有成熟的模式(Rc<RefCell<T>>Arc<Mutex<T>> 等)
  2. 成熟的生态系统:Rust 在 WebAssembly、异步运行时(Tokio)、网络编程等领域有大量经过生产验证的库
  3. 更好的错误处理文化:Rust 的 Result<T, E> 类型系统迫使开发者显式处理错误,而不是像许多 Zig 代码那样粗暴地用 try/catch 一抛了之

但 Sumner 心里清楚:手动将 50 万行 Zig 重写成 Rust,"一个小型工程师团队需要整整一年"。这意味着在重构期间,Bug 修复、安全补丁和新功能开发将全部暂停——对于一个正在快速增长的项目来说,这是不可接受的代价。

于是他决定:让 AI 来做这件事


二、AI 重构的真相:Claude 是如何完成这项工作的?

2.1 工作规模

让我们先明确一下这次重构的技术规模:

原始代码:~50万行 Zig 代码
生成代码:~100万行 Rust 代码
耗时:11天
并发 Agent 数量:约 50 个 Claude Code 工作流
峰值代码生成速度:每分钟约 1300 行
总成本:约 16.5 万美元(按 API 定价)
测试:超过 100 万条断言,100% 通过,无跳过

这个数字意味着什么?如果用传统的"一个工程师一天写 100 行高质量代码"来估算,100 万行代码需要 10000 个人天,即约 40 个工程师工作一年。而 Claude 在 11 天内完成了。

2.2 Claude Fable 的角色

Sumner 提到,这次迁移中 Claude Fable 承担了大部分繁重的工作。Claude Fable 是 Anthropic 开发的一个代码翻译/适配框架,它不是简单地将 Zig 代码逐行翻译成 Rust,而是:

  1. 理解语义等价性:Zig 和 Rust 的语法差异很大(例如 Zig 的 error 类型对应 Rust 的 enum,Zig 的 comptime 对应 Rust 的 const generics/macro),需要语义级别的等价映射
  2. 处理依赖图:将 Zig 的模块依赖图映射到 Rust 的 crate 结构
  3. 应用 Rust 惯用法:自动将 Zig 风格的错误处理转换为 Rust 的 Result 链,将 Zig 的 defer 语句转换为 Rust 的 drop 模式

2.3 测试先行:为什么 100 万条断言是重构的基石

Sumner 做出了一个关键决策:先用 Zig 版本的 Bun 积累一个足够全面的测试套件,然后用这个测试套件来验证 Rust 版本是否在功能上完全等价。

这个思路其实来自 C++ 领域的"协议测试"(Protocol Testing)理念:不测实现,只测行为。只要输入-输出关系在两个版本中完全一致,就认为迁移成功。

Bun 的测试套件包含超过 100 万条断言,覆盖:

  • 所有 JavaScript 标准库 API(JSON.parseArray.prototype.map 等)
  • Bun 特有的 API(Bun.file()Bun.serve() 等)
  • 文件系统操作、网络请求、数据库连接
  • 性能基准测试(确保 Rust 版本不比 Zig 版本慢)

最终结果:Rust 版本在所有受支持平台上 100% 通过所有测试,没有跳过或删除任何测试项。

2.4 HashiCorp 联合创始人的震惊

HashiCorp 联合创始人 Mitchell Hashimoto 在 X 平台上发表评论:

"以那样的薪资水平,工程师绝对不可能在 11 天内达成 Claude 所完成的里程碑。"

这番话在技术社区引发了广泛讨论。有人惊叹 AI 编程能力的天花板已经被突破,也有人质疑:速度真的等于质量吗?


三、Andrew Kelley 的反击:为什么这不只是"语言之争"

3.1 Kelley 的核心论点

Zig 创始人 Andrew Kelley 在一篇标题为"我对 Bun 用 Rust 重写的看法"的博文中,给出了非常直接的评价。他的核心论点是:

这不是关于 Rust vs Zig 的技术对比,而是关于工程文化的价值观差异。

Kelley 写道:

"早在获得大语言模型(LLM)访问权限之前,Sumner 就已经在写一团糟糕的代码了。"

这句话虽然尖锐,但它指向了一个真实的担忧:Bun 的代码质量问题不是 Zig 语言造成的,而是开发团队的风格问题。Kelley 指出,Bun 的代码中存在大量激进的功能发布、粗糙的错误处理和累积的技术债务——这些问题在任何语言中都会出现。

他进一步指出:

  1. Bun 是 Zig 语言最大的知名项目之一,在 2025 年被 Anthropic 收购前,Bun 是 Zig 软件基金会的定期捐助者。当 Bun 的代码质量出现问题时,外部观察者很自然地会将问题归咎于 Zig 语言本身。

  2. Bun 的 Rust 化并不能证明 Rust 比 Zig 更优秀,因为测试套件本身是针对 Bun 的需求设计的。如果测试套件在 Zig 代码中都没能发现那些 Bug,那么它在 Rust 代码中同样可能遗漏 Bug。

  3. 100 万行 AI 生成的代码没有任何人工审核,这是一个危险的先例。

3.2 Zig 拒绝 AI 贡献的政策

Kelley 还透露了一个有趣的细节:Anthropic 收购 Bun 之前,Bun 团队曾维护着一个 Zig 分支,据称该分支使调试编译速度提高了 4 倍。但当他们尝试将这个优化贡献提交给 Zig 主项目时,Zig 项目以"不接受基于 AI 的贡献"为由拒绝了这些改动

这个政策背后是 Zig 社区对 AI 生成代码质量的深度担忧。Zig 项目收到了大量由大语言模型生成的提交代码,其中大部分质量堪忧。Kelley 认为,如果 Zig 放松对 AI 代码的审核标准,代码库将迅速被技术债务淹没。

3.3 两个项目截然不同的价值观

Kelley 用一个简洁的对比来描述两种文化的差异:

Zig 社区Bun / Anthropic
核心理念显式优于隐式,简洁优于功能速度优于一切,快速迭代
对 AI 代码的态度不接受未经人工审核的 AI 贡献大量使用 AI 生成代码
错误处理鼓励显式处理每个错误经常用 try/catch 笼统处理
发布节奏保守,优先稳定性激进,快速发布新功能
技术债务尽量避免接受并持续累积

这个对比揭示了一个更本质的问题:软件工程从来没有银弹。每一种技术选择都伴随着价值观的取舍——你想要速度,还是想要稳健?想要功能,还是想要简洁?


四、技术深度:Zig 与 Rust 在系统编程上的根本差异

4.1 内存管理模型的根本分歧

要理解为什么 Bun 在 Zig 上会遇到架构问题,我们需要深入理解 Zig 和 Rust 的内存管理哲学。

Rust:所有权 + 借用检查器 + 生命周期

Rust 的内存管理通过编译器的所有权系统实现,无需垃圾回收器:

// Rust:所有权的转移是显式的
fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 的所有权转移到 s2
    // println!("{}", s1); // 编译错误:s1 不再有效
    println!("{}", s2); // 正确
}

// Rust:借用允许暂时使用值而不获得所有权
fn calculate_length(s: &String) -> usize {
    s.len() // 借用 s,不需要所有权
}

fn main() {
    let s = String::from("hello");
    let len = calculate_length(&s); // s 仍然有效
    println!("Length of '{}' is {}", s, len);
}

// Rust:Rc<T> 和 RefCell<T> 的组合用于内部可变性
use std::cell::RefCell;
use std::rc::Rc;

let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
shared.borrow_mut().push(4); // 在 Rc 的只读引用下修改内部值

Zig:显式内存管理 + comptime + defer

Zig 不提供任何隐藏的内存管理机制,一切都由程序员显式控制:

const std = @import("std");

// Zig:手动内存分配
fn createString(allocator: *std.mem.Allocator) ![]const u8 {
    const str = try allocator.alloc(u8, 12);
    std.mem.copy(u8, str, "hello world");
    return str;
}

// Zig:defer 语句——相当于 Go 的 defer,但更强大
fn readFile(path: []const u8) !void {
    const file = try std.fs.cwd().openFile(path, .{});
    defer file.close(); // 无论函数如何退出,file 都会被关闭
    
    var buffer: [1024]u8 = undefined;
    const bytes_read = try file.read(&buffer);
    // 处理 bytes_read
}

// Zig:comptime 块——编译时执行代码
fn Matrix(comptime rows: usize, comptime cols: usize, comptime T: type) type {
    return struct {
        data: [rows][cols]T,
        fn at(self: *const @This(), row: usize, col: usize) T {
            return self.data[row][col];
        }
    };
}

Zig 的哲学是"没有隐藏的控制流,没有隐藏的内存分配"。但当你在写一个像 Bun 这样的混合系统时,这意味着你需要:

  1. 维护一个 JSC 的 GC 上下文(不是你控制的)
  2. 维护你自己的内存分配器(你需要控制的)
  3. 在两者之间安全地传递数据

在 Rust 中,这个问题有成熟的解决方案:Arc<Rc<T>> 可以在 GC 管理的对象和 Rust 管理的对象之间安全地共享引用。在 Zig 中,这需要你自己实现引用计数和生命周期管理——一个稍微不慎就会引入 use-after-free 或数据竞争。

4.2 错误处理的风格差异

Rust 的 Result 模式:

use std::fs::File;
use std::io::Read;

fn read_config(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut file = File::open(path)?; // ? 操作符传播错误
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    
    // 如果 JSON 解析失败,也会传播错误
    let config: Config = serde_json::from_str(&contents)?;
    Ok(contents)
}

fn main() {
    match read_config("config.json") {
        Ok(contents) => println!("Config: {}", contents),
        Err(e) => eprintln!("Error: {}", e),
    }
}

Zig 的错误集模式:

const std = @import("std");

const FileError = error {
    NotFound,
    PermissionDenied,
    Unexpected,
};

fn readConfig(path: []const u8) FileError![]const u8 {
    const file = std.fs.cwd().openFile(path, .{}) catch |err| {
        switch (err) {
            error.FileNotFound => return FileError.NotFound,
            error.AccessDenied => return FileError.PermissionDenied,
            else => return FileError.Unexpected,
        }
    };
    defer file.close();
    
    const content = file.readToEndAlloc(&std.heap.page_allocator, 1024 * 1024) catch |err| {
        return FileError.Unexpected;
    };
    return content;
}

注意两者最关键的差异:Rust 的 ? 操作符在错误路径上会导致函数提前返回并传播错误,但编译器能精确地追踪哪些代码路径可能失败。Zig 的 try/catch 虽然功能类似,但 Kelley 指出的一个问题是:很多 Zig 开发者倾向于用宽泛的 catch |_| {} 来吞掉所有错误,而不是显式处理每一种错误情况。

这正是 Kelley 所说的"Bun 代码中错误处理代码拙劣"的根本原因——不是 Zig 的 try/catch 本身有问题,而是开发者的习惯使得错误处理变得粗糙。

4.3 异步运行时:Bun 的特殊挑战

Bun 的异步 I/O 也是一个技术难点。Node.js 使用 libuv 作为跨平台异步 I/O 库,Bun 则需要在 JSC 的事件循环和底层 I/O 系统之间做桥接。

Bun 在 Zig 时代的异步架构(简化版):

// Zig 时代的 Bun FFI 绑定——需要手动管理 JSC 上下文和系统 I/O 的交互
const JSC = @import("jsc");
const uv = @import("uv"); // libuv 的 Zig 绑定

// 每个 JavaScript Promise 对应一个 Zig 协程
fn makeHttpRequest(url: []const u8, resolve: *JSC.JSValue, reject: *JSC.JSValue) !void {
    const req = uv.http_request_init(url) catch return;
    
    // 这里的问题:JSC 的 GC 和 libuv 的异步回调之间没有类型安全的桥接
    // 当 libuv 回调触发时,我们需要在 JSC 的上下文中执行 resolve/reject
    // 但 Zig 没有语言级别的协程支持,需要手动管理协程栈
    req.onComplete = struct {
        fn callback(data: []const u8, err: ?anyerror) void {
            if (err) |e| {
                // 在 JSC 上下文中执行 reject
                JSC.reject(reject, e);
            } else {
                // 在 JSC 上下文中执行 resolve
                JSC.resolve(resolve, data);
            }
        }
    }.callback;
    
    uv.http_request_send(req);
}

在 Rust 中,这个问题可以优雅地解决:

Rust 时代的 Bun 异步架构(简化版):

use tokio::runtime::Runtime;
use tokio::net::TcpStream;
use std::future::Future;

// Bun 的 Rust 重构版本使用 Tokio 作为异步运行时
pub struct BunHttpRequest {
    url: String,
    rt: Runtime,
}

impl BunHttpRequest {
    pub fn new(url: String) -> Self {
        Self {
            url,
            rt: Runtime::new().unwrap(),
        }
    }
    
    // Rust 的 async/await 提供类型安全的协程管理
    pub async fn fetch(&self) -> Result<Vec<u8>, BunError> {
        // Tokio 运行时自动管理协程栈
        // Arc<Mutex<JSCContext>> 提供线程安全的 JS 上下文访问
        let jsc_ctx = self.jsc_context.clone();
        
        let response = self.http_get(&self.url).await?;
        
        // 在 JS 上下文中 resolve Promise——编译器保证类型安全
        jsc_ctx.with(|ctx| {
            ctx.resolve_promise(&response);
        });
        
        Ok(response)
    }
    
    async fn http_get(&self, url: &str) -> Result<Vec<u8>, BunError> {
        // Tokio 的 async DNS + TCP 连接
        let response = reqwest::get(url).await?;
        let body = response.bytes().await?;
        Ok(body.to_vec())
    }
}

Rust 的 async/await + Tokio 组合在这个场景中提供了三个 Zig 没有的优势:

  1. 类型安全的协程管理:编译器追踪每个 Future 的生命周期
  2. 成熟的运行时:Tokio 经过了数千个生产项目的验证
  3. 与 GC 的安全桥接:通过 Arc<Mutex<JSCContext>> 在多线程环境中安全地访问 JSC 上下文

五、AI 代码质量的工程学思考

5.1 测试覆盖率能否代替代码审查?

Claude 生成的 100 万行 Rust 代码没有经过人工审核,唯一的质量保证是测试套件。Sumner 认为这没有问题,因为测试套件覆盖了所有功能行为。

但这个逻辑有一个根本性的漏洞:测试不能证明代码是正确的,只能证明代码在测试覆盖的路径上是正确的。

让我们看一个具体的例子:

// Claude 生成的代码通过了所有测试,但可能存在隐藏问题
impl BunFile {
    // 测试通过了,因为所有测试用例都使用了 UTF-8 编码的文件
    // 但生产环境中有大量 GBK 编码的文件——这个问题只有在用户报告后才被发现
    pub fn read_to_string(&self) -> Result<String, BunError> {
        let bytes = self.read_all()?;
        // 假设所有文件都是 UTF-8——这是一个隐藏的假设
        Ok(String::from_utf8(bytes)
            .map_err(|_| BunError::InvalidUtf8)?)
    }
}

测试套件永远无法覆盖所有现实世界的数据模式。只有经过人工审核的代码才能发现"测试通过但逻辑不对"的问题。

5.2 AI 代码生成的模式问题

当前的 LLM 代码生成有一个显著的倾向:倾向于生成"能通过测试"的代码,而不是"正确且优雅"的代码。

这是因为 LLM 的训练目标是预测"下一个 token",而评判标准是"这个 token 是否符合人类期望"。当测试套件足够全面时,LLM 会学到"符合测试的模式",但这并不等同于"符合软件工程最佳实践的模式"。

具体来说:

维度人工编写AI 生成
正确性(测试覆盖路径)
可读性因人而异通常中等偏低
边界情况处理依赖经验依赖训练数据覆盖
性能优化可主动设计被动优化
安全漏洞意识驱动数据驱动

5.3 什么是负责任的 AI 代码生成?

这次事件给整个行业提出了一个重要问题:我们应该如何将 AI 代码生成纳入工程工作流?

一个务实的答案可能是:

# 负责任的 AI 代码生成工作流
def ai_code_review_workflow():
    ai_generated_code = claude.generate_code(spec)
    
    # 步骤 1:静态分析
    issues = static_analyzer.analyze(ai_generated_code)
    for issue in issues:
        if issue.severity == "high":
            raise CodeReviewError(f"High severity issue: {issue}")
    
    # 步骤 2:测试验证
    test_coverage = coverage_tool.run(ai_generated_code)
    if test_coverage < 85:
        raise CodeReviewError("Test coverage below threshold")
    
    # 步骤 3:人工审核(不可跳过)
    human_approval = request_human_review(ai_generated_code)
    if not human_approval:
        raise CodeReviewError("Human review rejected")
    
    # 步骤 4:模糊测试(针对安全性敏感代码)
    if is_security_critical(ai_generated_code):
        fuzz_results = fuzz_tester.run(ai_generated_code)
        if fuzz_results.vulnerabilities > 0:
            raise CodeReviewError("Fuzz testing found vulnerabilities")
    
    return merge_code(ai_generated_code)

关键是:人工审核是不可跳过的步骤。即使 AI 生成的代码通过了所有自动化检查,仍然需要工程师从架构、可读性、安全性等多个维度进行人工审查。


六、Rust 的成熟生态:Bun 重构成功的隐藏功臣

6.1 为什么 Rust 的生态对 Bun 有利

Bun 的 Rust 重构能够快速完成,离不开 Rust 成熟的生态系统。以下是几个关键的依赖库:

异步运行时:Tokio

// Bun 的 HTTP 服务器在 Rust 版本中使用 Tokio
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

pub async fn start_server(addr: &str) -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind(addr).await?;
    println!("Bun server listening on {}", addr);
    
    loop {
        let (mut socket, _) = listener.accept().await?;
        
        tokio::spawn(async move {
            let mut buf = vec![0u8; 1024];
            loop {
                let n = match socket.read(&mut buf).await {
                    Ok(n) if n == 0 => return,
                    Ok(n) => n,
                    Err(e) => {
                        eprintln!("Error reading: {}", e);
                        return;
                    }
                };
                
                if socket.write_all(&buf[..n]).await.is_err() {
                    return;
                }
            }
        });
    }
}

Tokio 是 Rust 生态中最成熟的异步运行时,拥有:

  • 超过 5000 个生产用户(包括 AWS、Discord、Dropbox 等)
  • 完善的错误处理和回压机制
  • 与所有主流 Rust 网络库的兼容性

HTTP 客户端:reqwest

use reqwest::Client;

// 替换 Zig 版本中手写的 HTTP 实现
pub async fn fetch_with_headers(url: &str, headers: &HashMap<&str, &str>) -> Result<Response, Error> {
    let client = Client::new();
    let mut request = client.get(url);
    
    for (key, value) in headers {
        request = request.header(*key, *value);
    }
    
    request.send().await
}

序列化:serde

use serde::{Deserialize, Serialize};

// 所有数据结构自动获得序列化/反序列化能力
#[derive(Debug, Serialize, Deserialize)]
pub struct BunResponse {
    pub status: u16,
    pub headers: HashMap<String, String>,
    pub body: Vec<u8>,
}

// serde_json 是最快的 Rust JSON 库之一
let json_str = serde_json::to_string(&response).unwrap();

6.2 迁移的工程实践:逐步替换而非一次性重写

虽然 Sumner 描述的是"11天完成重构",但从工程实践角度,这次迁移很可能是渐进式的

// 阶段 1:建立 Rust 基础框架(与 Zig 代码并行运行)
// 在 Cargo.toml 中添加 bun 的 Rust crate
[dependencies]
bun_runtime = { path = "crates/bun_runtime" }
bun_http = { path = "crates/bun_http" }

// 阶段 2:通过 FFI 调用验证 Rust 代码的正确性
// Rust 代码先用 FFI 被 Zig 代码调用
extern "C" {
    fn bun_rust_http_init() -> *mut BunHttpContext;
    fn bun_rust_http_fetch(ctx: *mut BunHttpContext, url: *const c_char) -> *mut BunResponse;
}

// 阶段 3:逐模块替换
// 从最独立、最少依赖的模块开始替换(如哈希函数)
pub fn sha256(data: &[u8]) -> [u8; 32] {
    use std::crypto::digest::Digest;
    let mut hasher = Sha256::new();
    hasher.input(data);
    let mut result = [0u8; 32];
    hasher.result(&mut result);
    result
}

// 阶段 4:流量切换
// 通过 feature flag 控制使用 Rust 还是 Zig 实现
#[cfg(feature = "rust_impl")]
pub fn http_fetch(url: &str) -> BunResponse {
    runtime().block_on(BunHttpClient::new().fetch(url))
}

#[cfg(not(feature = "rust_impl"))]
pub fn http_fetch(url: &str) -> BunResponse {
    // 委托给 Zig 实现
    extern { fn bun_zig_http_fetch(url: *const c_char) -> *mut BunResponse; }
    unsafe { BunResponse::from_ptr(bun_zig_http_fetch(url.as_ptr())) }
}

这种渐进式迁移策略的好处是:始终有一个可运行的版本,任何时候发现问题都可以回退。


七、行业影响:这次重构告诉我们什么?

7.1 AI 编程能力的天花板在哪里?

Sumner 的实验证明了当前 AI 编程能力的几个关键事实:

  1. 大规模代码生成是可行的:75万行级别的代码迁移在11天内完成,测试100%通过
  2. 成本已经进入商业可行区间:16.5万美元对于一个中型技术决策来说是可接受的(相当于1-2个工程师一年的薪资)
  3. 但质量保证体系必须同步升级:当代码生成速度超过人工审核速度时,我们需要重新定义"代码审查"的含义

7.2 编程语言的选择标准正在改变

传统的编程语言选择标准是:性能、生态、社区。这次事件之后,我们需要加入第四个维度:

AI 可编辑性(AI-Editability)

一门语言如果能被 AI 高效地生成、理解和修改,它在 AI 时代就有额外的竞争优势。Rust 在这方面的表现可能优于 Zig,因为:

  • Rust 拥有更丰富的类型系统,AI 可以基于类型信息做更精确的代码生成
  • Rust 的错误处理模式(Result<T, E>)使 AI 能够更好地理解函数的契约
  • Rust 的文档工具(rustdoc)和代码格式化工具(rustfmt)提供了标准化的代码风格

7.3 "AI 重构"成为可能的新场景

对于那些历史上因为成本太高而无法重构的项目(Bun 就是一个典型例子),AI 编程提供了一个全新的可能性:如果迁移成本从"1年团队时间"降低到"11天 AI 时间",那么那些技术上已经陷入困境的项目将获得第二次生命。

但我们必须清醒地认识到:能迁移不等于应该迁移。如果原始架构存在根本性缺陷(就像 Bun 的 GC/手动混合管理问题),迁移到新语言只是解决了表面问题,架构重构才是治本之道。


八、生产踩坑清单:给你的 15 条建议

基于这次事件的分析,以下是我总结的生产实践建议:

架构设计阶段

  1. 避免语言边界模糊的混合架构:GC 语言和手动管理语言的混合系统(如 Bun 遇到的问题)应该在架构设计阶段就识别并解决
  2. 选择有成熟生态的语言:Rust 的 Tokio/reqwest/serde 生态是 Bun 重构成功的关键因素之一
  3. 为 AI 审核留出预算:在项目规划阶段就将 AI 代码生成后的审核时间纳入估算(建议 1:1,即 AI 生成 100 行,人工审核 100 行)

AI 代码生成阶段

  1. 测试套件先行:在用 AI 生成代码之前,先建立完善的测试套件
  2. 使用类型系统约束 AI 输出:Rust 的强类型系统让 AI 生成的代码更可靠
  3. 不要跳过代码审查:即使 AI 生成的代码通过了所有测试,人工审核仍然不可替代
  4. 关注边界情况和安全漏洞:AI 倾向于生成"主流路径"的代码,边界情况容易被忽略

重构执行阶段

  1. 渐进式替换优于一次性重写:始终保持一个可运行的版本
  2. 使用 Feature Flag 控制切换:允许随时回退到旧实现
  3. 保留原始代码的注释和文档:AI 翻译过程中会丢失大量的设计意图

质量保证阶段

  1. 模糊测试(Fuzz Testing):对 AI 生成的代码进行额外的模糊测试
  2. 性能基准测试:确保重构不引入性能退化
  3. 长期技术债务监控:记录 AI 生成代码中发现的潜在问题,持续追踪

语言选择阶段

  1. 评估"AI 可编辑性":在语言选型时考虑该语言被 AI 理解和生成的难易程度
  2. 不要被性能数字蒙蔽:Bun 在 Zig 上很快,但维护成本更高。选择语言时不能只看基准测试

九、展望:2026 年的软件开发方法论

这次 Bun 重构事件将成为软件工程史上一个重要的案例研究。它告诉我们:

  1. AI 编程已经从"辅助工具"升级为"核心生产力":当 50 万行代码的迁移可以在 11 天内完成时,我们需要重新定义"不可能的项目"
  2. 工程文化和价值观的重要性:技术选择从来不是纯粹的技术决策,它反映了团队和社区的价值观
  3. 测试是重构的基石:没有 100 万条断言的测试套件,就没有 100% 通过的 Rust 版本

但我们也必须清醒地认识到:速度不等于质量。Kelley 的批评虽然尖锐,但它提醒我们,在追求 AI 速度的同时,不能忘记软件工程的根本——代码是写给人看的,顺便让机器执行。

未来的软件开发,可能是这样一幅图景:AI 负责生成代码,人类的角色从"代码写作者"转变为"系统设计者"和"质量守门人"。这不是人类被替代,而是人类角色的升级——从执行层上升到设计层和决策层。

就像工业革命用机器替代了体力劳动,但没有消灭人类工作者一样,AI 革命将会替代重复性的编程劳动,但不会消灭程序员——它消灭的是"不思考的程序员",而那些真正理解系统、能够设计架构、把控质量的工程师,将变得比以前任何时候都更珍贵。


【附】相关链接与参考文献

  • Bun 官方博客(Jarred Sumner 的迁移说明):https://bun.sh
  • Zig 官方博客(Andrew Kelley 的回应):https://ziglang.org
  • Bun GitHub 仓库:https://github.com/oven-sh/bun
  • Zig GitHub 仓库:https://github.com/ziglang/zig
  • The Register 报道:https://www.theregister.com/devops/2026/07/14/zig-creator-calls-buns-claude-rust-rewrite-unreviewed-slop/
  • Tokio 异步运行时:https://tokio.rs
  • reqwest HTTP 客户端:https://github.com/seanmonstar/reqwest

推荐文章

18个实用的 JavaScript 函数
2024-11-17 18:10:35 +0800 CST
一些高质量的Mac软件资源网站
2024-11-19 08:16:01 +0800 CST
宝塔面板 Nginx 服务管理命令
2024-11-18 17:26:26 +0800 CST
程序员茄子在线接单