Bun 用四个月把 53.5 万行 Zig 重写为 Rust:内存安全如何改变运行时工程

2026-09-21 35 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

Bun 的创建者 Jarred Sumner 宣布,Bun 这款 JavaScript/TypeScript 运行时、打包器和包管理器,已经完成从 Zig 到 Rust 的重写。此次迁移涉及约 53.5 万行代码,原本预计需要一年,但在 AI 辅助下,项目用了四个月完成。

这并不只是一次语言替换。对运行时、编译器和打包工具而言,内存生命周期贯穿解析、缓存、并发执行和原生扩展等多个边界。重写的核心目标,是借助 Rust 的所有权系统和借用检查器,减少反复出现的内存安全漏洞与泄漏问题。

为什么内存安全会成为重写动因

Zig 允许开发者显式管理内存,并提供了比较清晰的底层控制能力。但这种控制也意味着,团队需要持续维护分配器、释放路径、错误处理和跨线程数据访问之间的约束。

在一个大型 JavaScript 运行时中,典型风险包括:

  • 一个对象在仍被使用时提前释放,形成 use-after-free。
  • 错误路径遗漏释放操作,导致内存泄漏。
  • 多个组件对同一块内存的所有权理解不一致。
  • 缓存、异步任务和原生 API 之间存在生命周期错配。

Rust 的借用检查器不能自动修复所有逻辑错误,但它能把大量“谁负责释放、这个引用能活多久、多个线程能否同时访问”的约束前移到编译阶段。对于运行时这类长期运行、并发密集且资源敏感的程序,这种约束具有直接价值。

AI 加速了迁移,但没有取消工程约束

从一年压缩到四个月,AI 辅助是这次迁移中最引人注目的部分。大规模重写中,AI 可以帮助生成重复性的类型转换、API 适配、测试迁移和编译错误修复建议。

不过,AI 更擅长处理局部转换,较难独立判断以下问题:

  • 新旧实现是否保持相同的边界行为。
  • 某个生命周期设计是否适合整个模块,而不只是让当前函数通过编译。
  • 性能回退来自算法、分配策略,还是锁竞争。
  • 失败路径是否覆盖了真实生产环境中的资源清理。

因此,AI 让“写出能编译的 Rust”更快,却不会自动提供正确的架构。迁移仍然需要基准测试、回归测试、模糊测试以及对内存占用和吞吐量的持续观测。

一个最小的 Rust 所有权示例

下面的程序可以直接运行,展示 Rust 如何阻止一个已经转移所有权的值被继续使用。它不是 Bun 的实现代码,而是理解这类重写动机的最小示例。

将内容保存为 src/main.rs,并执行命令:

cargo new ownership-demo
cd ownership-demo
# 用下面的代码替换 src/main.rs
cargo run
fn consume_buffer(buffer: Vec<u8>) -> usize {
    buffer.len()
}

fn main() {
    let payload = vec![1_u8, 2, 3, 4];
    let size = consume_buffer(payload);

    println!("payload size: {size}");
    // println!("{}", payload.len());
    // 取消上一行注释后,编译器会报告 payload 已经被移动。
}

payload 被传入 consume_buffer 后,所有权转移给函数。调用方不能继续使用原变量,因此不会悄悄保留一个已经失效的引用。实际工程通常会根据需求选择借用、可变借用、引用计数或显式克隆,而不是把所有数据都复制一遍。

例如,只读处理可以改为借用:

fn inspect_buffer(buffer: &[u8]) -> usize {
    buffer.len()
}

fn main() {
    let payload = vec![1_u8, 2, 3, 4];
    let size = inspect_buffer(&payload);

    println!("payload size: {size}");
    println!("first byte: {}", payload[0]);
}

这种设计把生命周期关系写进类型和函数签名:函数只临时读取数据,调用方仍然拥有 payload。在大型代码库中,这类约束会逐渐扩展到缓存、解析树、任务队列和原生资源管理。

迁移项目应如何验证结果

类似 Bun 这样的重写,不能只用“编译通过”作为完成标准。更可靠的验证清单包括:

  1. 对比新旧实现的 CLI 行为、错误信息和退出码。
  2. 使用相同输入运行打包、解析和模块加载基准测试。
  3. 检查峰值内存、长期运行时的 RSS 和分配次数。
  4. 对解析器、模块解析和文件系统边界进行模糊测试。
  5. 在 CI 中保留 Sanitizer、并发测试和回归样例。
  6. 观察真实工作负载中的启动时间、吞吐量和尾延迟。

Rust 可以降低一类内存安全问题的发生概率,但它不会阻止逻辑泄漏、无限缓存、错误的资源上限或不合理的并发模型。迁移后的系统仍需要明确的性能预算和运行时监控。

给团队的实践建议

如果团队正在考虑类似的跨语言重写,可以把项目拆成几个可验证的阶段:先锁定外部行为和性能基线,再迁移边界清晰的模块,随后逐步替换共享数据结构和资源管理层。每个阶段都应保留旧实现与新实现的对照测试。

AI 适合承担机械、重复、反馈周期短的工作,例如生成初版转换代码和整理编译错误;架构决策、并发模型、错误语义和性能验收仍应由熟悉系统的工程师负责。

Bun 的案例说明,语言重写的价值不只在于更换语法。真正值得评估的是:新语言是否能把高频缺陷转化为编译期约束,团队是否有足够测试支撑行为一致性,以及迁移后的性能和维护成本是否符合产品目标。


相关推荐