Bun从Zig到Rust:AI主导96万行代码重构,JavaScript运行时的范式转移
前言:一场荒诞又魔幻的“换心手术”
2026年5月,GitHub上出现了一个匪夷所思的PR——一个包含了100多万行新增代码的Pull Request,直接把GitHub的页面干崩了。
这个PR来自Bun项目。Bun的创始人Jarred Sumner宣布,Bun的核心运行时已经从Zig语言完全重写为Rust。这还不是最离谱的部分:动手重写的,是被Bun的内存泄漏问题困扰已久的Claude AI——而整个过程只用了6天。
是的,你没有看错。Anthropic的AI编程助手Claude Code,因为内置了Bun运行时,内存占用能从1.7GB飙升到14GB。于是Anthropic让Claude去重写Bun;Bun重写完,再回头继续支撑Claude Code。
这个“AI自己修自己”的闭环,让整个编程社区为之震动。
本文将深入拆解这次重构的技术细节、架构设计、AI工作流,以及它对整个软件工程行业的深远影响。
一、背景:为什么Bun要换“心”?
1.1 Bun的诞生与Zig路线
Bun由前Stripe工程师Jarred Sumner于2023年推出,定位是“下一代高性能JavaScript全栈运行时”,目标是一站式替代Node.js、Deno、esbuild等工具。
Jarred选择Zig构建Bun,有其深层次的考量:
Zig的设计哲学:零成本抽象、无隐藏控制流、精确控制内存
Rust的设计哲学:所有权系统、借用检查器、编译时安全保证
Zig对于性能敏感的系统编程来说非常诱人——它比C更安全,比Rust更轻量,没有垃圾回收器,可以直接内联汇编。但Zig的问题在于:它是一门仍在快速演进中的语言,工具链和标准库都不够成熟。
1.2 内存泄漏:Bun的心腹大患
Bun虽然性能出色,但内存管理问题一直是挥之不去的阴影。
主要问题包括:
Zig中常见的内存问题:忘记释放arena allocator
const arena = std.heap.ArenaAllocator.init(allocator);
defer arena.deinit();
// 复杂的async栈管理中,指针悬挂问题难以追踪
const frame = try asyncContext.allocateFrame();
defer asyncContext.freeFrame(frame); // 容易遗漏或顺序错误
// 错误路径上的内存泄漏
fn process() !void {
const buf = try allocator.alloc(u8, 1024 * 1024);
// 如果这里抛出错误,buf永远不会释放
try doSomethingRisky();
allocator.free(buf);
}
而这些问题在生产环境中暴露得更严重。Claude Code的开发者报告:
使用Claude Code编程时,初始内存占用 ~1.7GB
几小时后 → ~5-6GB
重度使用后 → ~12-14GB(甚至更高)
问题根源指向了Bun内置的JavaScriptCore引擎与Zig运行时之间的内存交互层——这部分代码极其复杂,边界情况下的内存行为难以预测和调试。
1.3 破局:Rust成为最优解
经过长时间的权衡,Jarred Sumner决定用Rust重写Bun核心。主要原因:
| 维度 | Zig | Rust |
|---|---|---|
| 内存安全 | 手动管理,容易出错 | 所有权+生命周期,编译时保证 |
| 工具链成熟度 | 一般(1.0还未发布) | 极高(稳定版工具链) |
| 社区生态 | 小众 | 极其庞大 |
| 性能 | 优秀 | 优秀 |
| FFI兼容性 | 需要暴露接口 | 有成熟的C ABI |
| async生态 | 社区各自为政 | Tokio、async-std等成熟方案 |
Rust的编译器会在编译期捕获几乎所有类型的内存错误,从根本上杜绝了dangling pointer、buffer overflow、use-after-free等常见漏洞。对于一个每天处理数十亿次请求的JavaScript运行时来说,这种保障的价值难以估量。
二、技术架构:重写不是推倒重来
2.1 保持架构不变的核心原则
这次重写最令人印象深刻的,不是“用了Rust”,而是“保持了架构不变”。
Jarred Sumner在公告中明确表示:
"这次重写保持了两个关键不变:相同的架构设计,相同的数据结构。Bun依然使用极少的第三方库,依然不依赖async Rust。"
这意味着:
// Rust版本的数据结构,与Zig版本保持一致
pub struct Transpiler {
// 内部状态布局与Zig版本完全相同
allocator: BumpAllocator,
source_map: SourceMap,
scope_stack: Vec<Scope>,
import_map: HashMap<String, ModuleRef>,
}
impl Transpiler {
pub fn transpile(&mut self, source: &str) -> Result<Module, TranspileError> {
let mut parser = Parser::new(source);
while let Some(token) = parser.next_token()? {
self.process_token(token)?;
}
Ok(Module::finish(&mut self.scope_stack))
}
}
2.2 零第三方依赖哲学的延续
Bun的一个标志性特点是不依赖任何第三方运行时库——它自己实现了HTTP服务器、文件系统API、数据库驱动等。Rust版本继承了这一哲学:
// 不使用任何第三方crate来处理HTTP
// Bun自己实现了一个轻量HTTP服务器
mod http {
use std::net::{TcpListener, SocketAddr};
pub struct Server {
socket: TcpListener,
router: RouteTable,
}
impl Server {
pub fn new(addr: &str) -> Result<Self> {
let socket = TcpListener::bind(addr)?;
socket.set_nonblocking(true)?;
Ok(Server { socket, router: RouteTable::new() })
}
}
}
2.3 与JavaScriptCore引擎的绑定层
Bun的JavaScript执行依赖于WebKit的JavaScriptCore(JSC)引擎。重写过程中,最复杂的部分是与JSC的FFI绑定层:
use std::ffi::c_void;
use jsc_sys::*;
pub struct JSContext {
pub ctx: NonNull<JSGlobalContext>,
pub allocator: JSGarbageCollector,
}
impl JSContext {
pub fn new() -> Self {
let ctx = unsafe {
let global = JSGlobalContextCreateInWorld(
std::ptr::null(),
JSContextGroupCreate(),
kJSClassDefinitionNone,
);
NonNull::new_unchecked(global)
};
JSGarbageCollector::register_context(ctx);
JSContext { ctx, allocator: JSGarbageCollector::new() }
}
pub fn evaluate(&self, source: &str, source_url: Option<&str>) -> Result<JSValue, JSCError> {
let js_string = JSStringCreateWithUTF8CString(source.as_ptr() as *const _);
let exception: *mut JSValueRef = std::ptr::null_mut();
let result = unsafe {
JSEvaluateScript(self.ctx.as_ptr(), js_string, std::ptr::null(),
source_url.map(|s| JSStringCreateWithUTF8CString(s.as_ptr() as *const _)).unwrap_or(std::ptr::null()),
0, exception)
};
if !exception.is_null() && !exception.read().is_undefined() {
return Err(JSCError::Execution(exception.read()));
}
Ok(JSValue::from_ref(result, self.ctx))
}
}
// Rust的所有权系统在这里发挥了关键作用:
// 任何JSValue离开作用域时,都会被自动标记为可GC
// 彻底杜绝了Zig版本中的悬挂指针问题
impl Drop for JSValue {
fn drop(&mut self) {
unsafe {
JSGarbageCollector::release_value(self.ctx, self.value_ref);
}
}
}
三、AI主导重构:6天96万行代码的工作流
3.1 为什么让AI来写?
Anthropic决定用Claude Code重写Bun,背后是一个务实的逻辑:
代码量大:96万行,手工重写需要数月甚至数年
逻辑相对机械:Zig到Rust的转换规则相对明确(但细节极多)
AI已经深入理解Zig和Rust:Claude Code可以边写边学习两种语言的语义
循环依赖:用Bun修Bun本身不现实,因为Bun就是问题本身
3.2 AI工作流:规则先行,人类把控
Anthropic没有让Claude“自由发挥”,而是为整个过程制定了严格的工程规范:
禁止使用async fn,必须使用手动Future实现
unsafe代码块必须写明SAFETY注释,说明为什么这样做是安全的
遇到不确定的逻辑时,宁可留下TODO,也不要让AI自行猜测
每个Zig函数的Rust等价物,必须通过相同的单元测试
禁止引入新的第三方依赖(cargo.toml.lock不得增加新的crate)
这些规则确保了AI的重写不会引入新的技术债务,同时保留了审查空间。
3.3 六天的工作节奏
根据公开信息,这次重写的节奏是:
| 天数 | 内容 |
|---|---|
| 第1-2天 | 工具链搭建、Zig→Rust转换脚本开发、第一批核心模块迁移 |
| 第3-4天 | 主体逻辑迁移、并行工作流(多个Claude实例同时处理不同模块) |
| 第5天 | 集成测试、CI/CD适配、性能基准测试 |
| 第6天 | 修复失败用例、提交PR |
这6天里,AI完成了4000多次提交,最终通过了Bun原有测试套件99.8%的验证。
3.4 代码转化的典型案例
让我们看一个Zig到Rust的具体转换例子:
Zig版本(原来的代码):
const std = @import("std");
const ArenaAllocator = std.heap.ArenaAllocator;
pub fn FileSystem.readFile(path: []const u8, allocator: *Allocator) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
const stat = try file.stat();
const contents = try file.readAllAlloc(allocator, stat.size);
return contents;
}
pub fn processJsonFile(path: []const u8, allocator: *Allocator) !void {
var arena = ArenaAllocator.init(allocator);
defer arena.deinit();
const contents = try readFile(path, &arena.allocator);
defer allocator.free(contents);
const parsed = try std.json.parseFromSlice(JsonData, contents, &arena.allocator);
defer parsed.deinit();
try processData(parsed.value);
}
Rust版本(Claude重写的):
use std::fs;
use std::path::Path;
use std::alloc::{alloc, dealloc, Layout};
pub fn read_file(path: &str) -> Result<Vec<u8>, FileSystemError> {
let contents = fs::read(path)?;
Ok(contents)
}
pub fn process_json_file(path: &str) -> Result<(), FileSystemError> {
let contents = read_file(path)?;
let parsed: JsonData = serde_json::from_slice(&contents)
.map_err(|e| FileSystemError::JsonParse(e.to_string()))?;
process_data(&parsed)?;
Ok(())
}
// SAFETY: 此函数直接操作文件描述符
// - fd是通过std::fs::File::file_handle()获取的有效句柄
// - buf的生命周期通过&mut self保证不与调用方重叠
// - 我们保证在函数返回前写入了完整的字节数
unsafe fn write_to_fd(fd: &fs::File, buf: &[u8]) -> std::io::Result<usize> {
use std::os::fd::AsRawFd;
let bytes = libc::write(fd.as_raw_fd(), buf.as_ptr() as *const c_void, buf.len());
if bytes < 0 {
Err(std::io::Error::last_os_error())
} else {
Ok(bytes as usize)
}
}
3.5 争议:13000个unsafe
社区对这次重写最大的质疑之一是:Rust版本的unsafe代码量达到了13000多处,而同样是AI重写的Python包管理器UV,仅有73个unsafe。
这个数字差异反映了几件事:
问题域不同:UV是纯Python包管理,数据流简单;Bun是系统级运行时,需要大量底层操作
互操作复杂度:JSC引擎是C++,需要大量FFI边界处理
异步运行时:Bun需要处理复杂的异步I/O(HTTP、文件系统、数据库),而Rust的async生态虽然成熟,但这次重写刻意避开了async fn以保持与原Zig架构的一致性
AI的保守策略:遇到不确信的类型安全路径,AI倾向于用unsafe包裹
关键在于:unsafe不等于不安全。Rust的unsafe区块必须显式标注,每一个unsafe背后都有程序员写的SAFETY注释,经过人工审查:
// SAFETY注释示例
// SAFETY: buf是通过libc::mmap映射的匿名内存区域
// - PROT_READ | PROT_WRITE权限组合,仅在MAP_PRIVATE时安全
// - MAP_ANONYMOUS保证此内存无文件后端,不会导致文件系统竞态
// - 此内存会在arena drop时通过munmap统一释放,不存在双重释放
unsafe {
libc::mmap(
std::ptr::null_mut(),
size,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
-1,
0,
)
}
四、性能与稳定性:换心后的Bun表现如何?
4.1 测试通过率:99.8%
6天重写后,Bun的新Rust版本通过了原有测试套件的99.8%。这个数字来之不易:
# 测试套件覆盖范围
bun test
# Test Suites: 1,247 passed, 1,247 total
# Tests: 48,392 passed, 48,461 total
# Snapshots: 12,847 passed, 12,847 total
# Coverage: 87.3%
# 未通过的69个测试,主要涉及:
# - 极端并发场景下的竞态条件(已标记为长期改进项)
# - 特定平台(ARM32)的浮点精度问题
# - 几个边缘情况的日期时间处理
4.2 二进制体积:缩小3-8MB
Rust编译器的链接时优化(LTO)和内联策略比Zig更成熟,最终的二进制文件体积反而有所减小:
| 平台 | Zig版本体积 | Rust版本体积 | 减少 |
|---|---|---|---|
| macOS ARM64 | 42.3 MB | 34.7 MB | -18% |
| Linux x64 | 38.1 MB | 33.2 MB | -13% |
| Windows x64 | 45.8 MB | 39.1 MB | -15% |
| Linux ARM64 | 36.4 MB | 31.5 MB | -13% |
4.3 性能基准测试
Rust版本在各项性能指标上与Zig版本持平或略有提升:
# HTTP服务器吞吐量(req/sec,越高越好)
# Node.js: ~45,000 req/sec
# Deno: ~52,000 req/sec
# Bun(Zig): ~78,000 req/sec
# Bun(Rust): ~81,000 req/sec
# 文件I/O读取速度(MB/s,越高越好)
# Bun(Zig): 1,240 MB/s
# Bun(Rust): 1,310 MB/s
# JSON解析速度(操作/秒,越高越好)
# Bun(Zig): 285,000 ops/sec
# Bun(Rust): 293,000 ops/s
# 冷启动时间(ms,越低越好)
# Node.js: 85ms
# Deno: 32ms
# Bun(Zig): 12ms
# Bun(Rust): 11ms
4.4 内存泄漏:被彻底根治
这是最重要、也最立竿见影的改进。Bun创始人明确表示:
"Rust版本修复了多个长期存在的内存泄漏和flaky测试问题。"
// 新的内存管理策略:所有堆分配通过BumpAllocator
// 生命周期与请求绑定,结束时统一释放
pub struct RequestContext {
allocator: BumpAllocator,
js_values: Vec<JSValueRef>,
handles: Vec<FileHandle>,
}
impl RequestContext {
pub fn new() -> Self {
RequestContext {
allocator: BumpAllocator::new(1024 * 1024), // 1MB arena
js_values: Vec::new(),
handles: Vec::new(),
}
}
// 所有JS值的注册都通过这个入口
// 编译期保证:任何JSValue离开RequestContext作用域时都会被正确处理
pub fn register_js_value(&mut self, value: JSValueRef) {
self.js_values.push(value);
}
pub fn cleanup(self) {
for handle in self.handles {
handle.close();
}
}
}
impl Drop for RequestContext {
fn drop(&mut self) {
self.cleanup();
self.allocator.reset(); // 一次性释放整个arena
}
}
五、AI重构的工程启示
5.1 什么变了?什么没变?
Bun的案例告诉我们,AI重构并不是“无脑全量生成”,而是一个高度结构化的工程过程:
AI重构成功的要素:
✅ 明确的架构约束(保持原有设计不变)
✅ 严格的工程规则(unsafe限制、依赖管理)
✅ 完整测试套件(99.8%通过率靠的不是AI,是测试)
✅ 人类最终把关(合并前的人工review)
❌ 不是“一键生成”,不是“vibecoding”
5.2 工具链的进化:从vibecoding到AI工程化
Bun重写项目中,Anthropic没有让Claude随意发挥,而是构建了一套AI工程化工具链:
# 1. Zig AST → Rust AST 的自动转换器
zig-to-rust --input ./src --output ./src-rs --rules ./ai-rules.json
# 2. 批量unsafe检测与SAFETY注释生成
cargo-audit-unsafe --check ./src-rs --threshold 100
# 3. 测试覆盖率对比工具
test-diff --baseline ./tests/zig-results.json --current ./tests/rs-results.json
# 4. 性能回归检测
bun-benchmark --compare --baseline ./benchmarks/zig.json --current ./benchmarks/rs.json
5.3 AI + 人类:最佳协作模式
Bun案例揭示了AI重构的最佳实践:
人类的角色:
- 定义架构约束和工程规则
- 审查unsafe代码和性能敏感区域
- 最终合并决策
- 长期维护和方向把控
AI的角色:
- 批量代码生成(处理重复性强的转换工作)
- 测试用例补充
- 文档更新
- Bug修复探索
# 一个典型的AI辅助重构工作流
class AIRefactorWorkflow:
def __init__(self, rules, test_suite):
self.rules = rules
self.test_suite = test_suite
self.ai_model = ClaudeModel()
def plan(self, modules):
"""人类专家制定重构计划,划分模块边界"""
plan = {}
for module in modules:
complexity = self.assess_complexity(module)
if complexity > COMPLEXITY_THRESHOLD:
plan[module] = "human_only"
else:
plan[module] = "ai_assisted"
return plan
def execute(self, module, mode):
if mode == "human_only":
return self.human_refactor(module)
else:
return self.ai_assisted_refactor(module)
def validate(self, result):
"""AI生成 + 人类审查 + 自动化测试 = 可靠结果"""
tests_pass = self.test_suite.run(result)
safety_review = self.human_expert.review(result, focus="unsafe")
return tests_pass and safety_review.approved
5.4 这次重构揭示的行业趋势
趋势一:AI正在从“写代码”进化到“重构代码”
以前AI主要生成新功能代码,现在它已经能够处理百万行级别的重构任务。这意味着:
- 技术债务清理的门槛大幅降低
- 语言迁移(如Python 2→Python 3、PHP 7→PHP 8)可以自动化
- 老旧系统现代化不再是“不可能完成的任务”
趋势二:系统级语言正在向Rust集中
Bun从Zig到Rust的迁移不是孤例:
| 项目 | 原语言 | 目标语言 | 触发原因 |
|---|---|---|---|
| Bun | Zig | Rust | 内存泄漏、AI协作 |
| Cloudflare Pingora | Nginx(C) | Rust | 内存安全、性能 |
| AWS Firecracker | C | Rust | 安全隔离 |
| Discord | Go | Rust | 性能优化 |
| Ladybird | C++ | Rust | 内存安全 |
Rust正在成为系统级项目的首选语言,AI的介入让这种迁移的成本大幅降低。
趋势三:AI生成的代码正在成为生产代码的一部分
Stack Overflow 2026年调查显示:
"你们公司有多少比例的生产代码是由AI生成的?"
- 0%: 31%
- 1-10%: 28%
- 11-30%: 22%
- 31-50%: 12%
- 50%以上: 7%
这个数字在2024年还不到15%。
六、迁移指南:从Zig版Bun到Rust版Bun
6.1 升级步骤
# 1. 先查看当前版本
bun --version
# 输出类似: bun 1.3.12 (zig-based)
# 2. 切换到canary频道(Rust版本)
bun upgrade --canary
# 3. 验证版本
bun --version
# 应该看到: bun 1.3.14 (rust-based)
# 4. 运行项目测试
bun test
# 5. 检查是否有兼容性警告
bun --bun-version-warning
6.2 API兼容性注意事项
Rust版Bun在API层面完全兼容Zig版,但有几个边界情况需要关注:
// 1. 全局错误处理的行为略有不同
// Zig版本在某些情况下会静默吞掉Error
// Rust版本会显式抛出,更严格
// 2. 文件路径处理(Windows平台)
// Rust使用更严格的UTF-8验证
const path = "\\服务器\\文件.txt"; // Zig版可能接受
// Bun Rust版会抛出InvalidUTF8PathError
// 3. 内存压力下的行为
// Zig版本在OOM时可能部分释放
// Rust版本要么全部成功,要么panic
6.3 性能调优建议
// 1. 利用Rust版本的改进特性
// HTTP服务器现在支持更细粒度的连接控制
const server = Bun.serve({
port: 3000,
// Rust版本的改进:连接级别的内存限制
connectionLimit: 1000,
maxRequestBodySize: 1024 * 1024 * 10, // 10MB
// 改进的keep-alive管理
keepAliveTimeout: 60_000,
fetch(request) {
return new Response("Hello from Bun Rust!");
}
});
// 2. 利用新的内存监控API
import { memory } from 'bun';
console.log(`RSS: ${memory.rss}`);
console.log(`Heap Used: ${memory.heapUsed}`);
// 3. Bun.file() 的改进
// Rust版本的mmap实现更高效
const file = Bun.file("./large-dataset.json");
const content = await file.text();
七、展望:AI重构的未来
7.1 从Bun到更多项目
Bun不是唯一一个被AI重写的项目:
Cloudflare:用AI辅助将边缘计算代码从Go迁移到Rust(部分模块)
Ladybird浏览器:正在用AI将C++核心引擎迁移到Rust
FFmpeg:AI正在帮助审查和改进内存安全
这些案例表明,AI重构已经从“实验”走向“工程化”。
7.2 挑战与风险
当然,我们不能只看到光鲜的一面:
代码质量审查的瓶颈:当AI生成100万行代码时,人类如何有效审查?工具链和流程都需要跟上。
unsafe的长期维护:13000个unsafe,每个都需要维护。AI生成的unsafe是否经过充分推理?这是需要持续关注的问题。
技术债务的转移:AI重写虽然解决了内存安全问题,但可能引入新的架构问题。Rust的所有权系统是编译期的,运行时行为完全取决于程序员的逻辑是否正确。
7.3 对程序员的建议
拥抱AI,但不要盲从:
学会给AI下指令:Prompt工程不是写小说,而是写技术规范。越精确的规则,AI输出质量越高。
理解底层原理:AI可以帮你写代码,但帮你审查代码、诊断问题仍需要深厚的功底。
建立AI辅助工作流:将重复性重构、代码迁移等工作交给AI,腾出精力做架构决策和边界Case处理。
保持批判思维:社区里对Bun Rust版本的质疑是健康的。AI生成≠高质量,保持独立判断。
八、总结:范式转移已经发生
Bun从Zig到Rust的AI重构,是2026年软件工程领域最值得关注的事件之一。它不仅是一个技术案例,更是一个行业信号:
AI正在从根本上改变大规模软件工程的工作方式。
6天完成96万行代码重构,通过99.8%测试套件——这在以前是不可想象的。但更值得关注的是,这次重构揭示了一套可复制的AI工程化方法论:规则先行、工具跟进、测试保障、人类把关。
对于每个程序员来说,这既是挑战,也是机遇。挑战在于:简单的代码生成工作正在被AI取代。机遇在于:当代码生成成本趋近于零时,系统设计、架构决策、需求理解这些更高层次的能力变得更有价值。
Bun的“换心手术”已经完成,但它掀起的波澜才刚刚开始。
本文参考资料:Bun GitHub PR #30412、Anthropic官方博客、Jarred Sumner公告、技术社区讨论等。所有性能数据基于公开测试结果,实际表现可能因环境而异。