AI 主导的"换心手术":Bun 如何用 6 天从 Zig 重写为 Rust,以及它对软件工程意味着什么
2026年5月,JavaScript 运行时 Bun 完成了一次足以载入软件工程史册的壮举——在短短六天之内,将近百万行 Zig 代码重写为 Rust,而执行这次迁移的"工程师"正是让 Bun 陷入困境的同一个 AI:Claude。
这听起来像是一个程序员茶余饭后的玩笑。但它真实发生了。更重要的是,这件事背后隐藏着一个严肃的问题:当 AI 可以接管如此大规模的系统级重写时,传统的软件开发流程将何去何从?
本文将从技术细节、工程实践和行业影响三个维度,对这次事件进行一次完整的深度解析。我们会深入到代码层面,对比 Zig 和 Rust 在内存管理、并发模型、错误处理上的本质差异,分析 AI 迁移过程中可能遇到的语义鸿沟,以及这次重写对整个行业意味着什么。
一、事件始末:为什么 Bun 要"换心"
1.1 Bun 的技术选型历史
要理解这次重写为什么发生,我们需要先了解 Bun 的技术选型历史,以及 Zig 语言本身的设计哲学。
Bun 于 2024 年正式发布,创始人 Jarred Sumner 选择用 Zig 语言构建运行时核心。Zig 是一门新兴的系统级编程语言,由 Andrew Kelley 于 2016 年创建,以零成本抽象、无隐藏控制流和精确的内存控制著称。选择 Zig 的核心理由是性能——Zig 可以让你写出接近 C/C++ 性能的代码,同时保持比 C 更高的安全性。
Zig 的几个核心设计哲学值得深入理解:
显式优于隐式:Zig 拒绝宏和隐式的内存分配。所有的内存分配都必须显式调用,任何隐藏的后台行为都会被视为设计缺陷。这意味着你在 Zig 中写的每一行代码,其内存行为都是完全可预测的。
// Zig 的显式分配哲学
const std = @import("std");
// 每次分配都显式指定 allocator
pub fn processData(allocator: std.mem.Allocator, data: []const u8) ![]u8 {
var result = try allocator.alloc(u8, data.len);
@memcpy(result, data);
return result;
}
对比 Node.js 中类似的功能,V8 引擎在背后做了大量的隐式优化和内存管理,开发者几乎感知不到内存分配的存在。而在 Zig 中,这一切都是透明的。
** comptime:编译时计算的深度集成
Zig 有一个极为强大的特性叫做 comptime,允许你在编译时执行任意代码并生成值。这使得 Zig 可以实现零成本的抽象——类似于 C++ 的模板元编程,但语法更加一致和可预测。
// Zig comptime 示例:编译时计算配置
const DatabaseConfig = struct {
host: []const u8,
port: u16,
max_connections: usize,
// 编译时验证配置的有效性
comptime {
@compileLog("Database config type instantiated");
}
};
// 编译期生成特定类型的序列化代码
fn generateParser(comptime T: type) type {
return struct {
pub fn parse(bytes: []const u8) !T {
// 编译时展开的解析逻辑
var result: T undefined;
@memcpy(@as(*volatile [*]u8, @ptrCast(&result)), bytes);
return result;
}
};
}
无隐藏控制流
Zig 的函数调用没有任何隐藏的行为。你调用的就是实际执行的,不会有任何钩子、装饰器或者中间件在背后偷偷修改行为。这与 JavaScript/TypeScript 中的装饰器模式、Python 的上下文管理器等形成了鲜明对比。
这个设计哲学让 Zig 代码的行为完全可预测,但代价是开发者需要显式处理更多细节,包括错误处理、内存管理和资源清理。
1.2 Zig 的稳定性困境
然而,Zig 语言本身仍处于 0.x 版本阶段,语言特性和标准库都在快速演进中。这带来了一个根本性的矛盾:
- 语言在快速变化:Zig 的标准库 API、编译器行为和语言特性每年都在调整
- Bun 的代码量在增长:从最初的几万行增长到接近百万行
- 迁移成本随代码量指数增长:每升级一次 Zig 版本,都可能引入不兼容的变更
这种"语言和项目同步演进"的模式在实践中带来了大量维护负担。根据 Jarred Sumner 本人在社交媒体上的表述,他"厌倦了不断修复内存泄漏和稳定性问题",希望借助更安全的方式来构建代码。
1.3 内存泄漏的深层原因
Bun 的内存泄漏问题并非简单的 bug,而是一个系统性的问题。让我们深入分析一下:
JSC FFI 的复杂性
Bun 的核心是 JavaScriptCore(JSC)引擎,这是 WebKit 的 JavaScript 引擎。Bun 通过 FFI(外部函数接口)与 JSC 进行交互。这个交互层涉及大量的 C 指针操作和手动内存管理。
// Bun 的 JSC 绑定层示意(Zig)
const JSC = @import("bun_jsc");
// JSValue 的创建和销毁需要精确管理
pub fn createJSString(ctx: *JSC.JSContextRef, str: []const u8) !*JSC.JSValue {
const js_string = JSC.JSStringCreateWithUTF8CString(str.ptr);
// 这里如果出错但没有正确清理,会导致内存泄漏
const value = JSC.JSValueMakeFromJSONString(ctx, js_string);
// js_string 需要被显式释放,但错误路径容易被忽略
JSC.JSStringRelease(js_string);
return value;
}
在 Zig 中,资源清理通常依赖 defer 语句,但复杂的多层错误处理路径上,defer 的执行时机可能与预期不符,导致资源未被释放。
async/await 与调度器的内存压力
Bun 实现了一套自己的异步运行时,类似于 Go 的 goroutine,但基于 Zig 的手写调度器。每一个被 async 标记的函数都会创建一个状态机对象,这些对象在内存中积累,直到被 await 消费。
// Zig async 的内存模型
const std = @import("std");
async fn fetchData(url: []const u8) ![]u8 {
var client: HttpClient = undefined;
defer client.deinit(); // 如果这个 defer 没有执行,内存就泄漏了
const response = try client.fetch(url);
return response.body;
}
// 问题场景:早期返回路径
async fn problematicFetch(url: []const u8) ![]u8 {
var client: HttpClient = undefined;
defer client.deinit();
if (url.len == 0) {
return error.EmptyURL; // 这里 defer 确实会执行,没问题
}
// 复杂的嵌套逻辑中,defer 可能执行多次或不执行
const response = try client.fetch(url);
if (response.status_code != 200) {
return error.HttpError; // defer 正常执行
}
// 但如果在递归调用中呢?
if (try shouldRetry(url)) {
return fetchData(url); // 这里会不会有问题?
}
return response.body;
}
实际上,Bun 的异步调度器在处理大量并发任务时,状态机的释放时机存在竞态条件,导致某些已完成任务的状态机对象无法被正确回收。
1.4 Claude Code 的困境
受影响最严重的,是 Claude Code。
Claude Code 是 Anthropic 推出的 AI 编程助手,其底层运行时正是基于 Bun 构建。开发者描述的使用体验是这样的:
- 正常状态:Claude Code 启动后内存占用约 1.7GB
- 持续使用数小时后:内存占用飙升至 14GB+
- 后果:IDE 卡顿、响应延迟、甚至进程崩溃
一位开发者在 GitHub 上详细记录了这个问题:
进程:Claude Code (Bun runtime)
初始内存:1.7 GB
8小时后的内存:14.2 GB
16小时后的内存:23.8 GB(进程崩溃)
内存增长曲线不是线性的,而是呈加速趋势。这说明存在某种内存累积的机制——可能是某种缓存结构、状态机对象,或者是 FFI 层的引用计数出了问题。
Anthropic 的工程师在排查后确定:问题不在 Claude Code 本身,而在 Bun 运行时。修复这个问题的最佳方案,就是升级到修复后的 Bun 版本——而那个修复后的版本,需要先把 Bun 本身重写了。
讽刺的一幕出现了:Bug 的制造者和 Bug 的修复者竟然是同一个 AI——Claude。
二、技术细节:100万行代码,6天,99.8% 通过率
2.1 迁移规模全景
根据公开信息,这次迁移的关键数据如下:
| 指标 | 数值 |
|---|---|
| 代码行数 | 接近 100 万行 Zig 代码 |
| 迁移耗时 | 6 天 |
| 测试通过率 | 99.8% |
| 最终 commit 数 | 4000+ |
| 主要贡献者 | Claude (AI) |
| 代码审查者 | Claude (AI) |
| PR 合并者 | Claude (AI) |
这个数据本身就足够震撼。传统软件开发中,百万行级别的代码迁移通常需要:
- 3-6 个月 的规划期
- 6-18 个月 的执行期
- 10-30 人 的专职团队
- 大量的 代码审查和人工 QA
而 AI 在 6 天内完成了这一切。
2.2 迁移策略深度分析
从技术角度推测,这次迁移采用了分层推进的策略。以下是我对可能的技术路径的深度分析:
第一阶段:语义映射层建立(Day 1)
Zig 和 Rust 虽然都是系统级语言,但设计哲学有显著差异。在开始大规模迁移之前,AI 首先需要建立两种语言之间的语义映射规则。
// Zig: 内存分配
const allocator = std.heap.page_allocator;
const buffer = try allocator.alloc(u8, 1024);
defer allocator.free(buffer);
// Rust: 内存分配(等价映射)
use std::alloc::{alloc, dealloc, Layout};
fn allocate_buffer(size: usize) -> Result<&'static [u8], Error> {
let layout = Layout::array::<u8>(size)?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err(Error::OutOfMemory);
}
// 注意:这里没有直接的 defer,需要用 RAII 或闭包
Ok(unsafe { slice::from_raw_parts(ptr, size) })
}
关键差异:
- Zig 的
defer是函数作用域的,Rust 没有等价的语法,需要用let _ = DropGuard { ... }模式 - Zig 的
try自动传播错误,Rust 的?操作符行为类似但返回类型不同 - Zig 的
anyerror是匿名错误类型,Rust 通常需要显式定义错误枚举
// Rust 模拟 Zig 的 defer 模式
struct Defer<F: Fn()> {
f: Option<F>,
}
impl<F: Fn()> Defer<F> {
fn new(f: F) -> Self {
Defer { f: Some(f) }
}
}
impl<F: Fn()> Drop for Defer<F> {
fn drop(&mut self) {
if let Some(f) = self.f.take() {
f();
}
}
}
// 使用方式
fn zig_style_defer() -> Result<(), Error> {
let _defer = Defer::new(|| {
println!("cleanup happens here");
});
// 函数逻辑...
Ok(()) // defer 自动执行
}
第二阶段:分层迁移(Day 2-5)
百万行代码不可能一次性处理。合理的做法是按依赖关系从底层到上层逐步迁移:
Layer 0 - 基础抽象层
├── 内存分配器(page, arena, fixed buffer)
├── 错误处理基础设施
└── comptime 宏的 Rust 等价实现
↓
Layer 1 - 系统接口层
├── 文件系统操作
├── 网络 Socket API
├── 进程管理
└── 时间/时钟系统
↓
Layer 2 - JavaScript 引擎绑定层
├── JavaScriptCore FFI
├── JSContext 管理
├── JSValue ↔ Rust 类型转换
└── 闭包和函数的跨语言调用
↓
Layer 3 - Web API 实现层
├── fetch / Response / Request
├── Buffer / ArrayBuffer
├── setTimeout / setInterval
├── console API
└── crypto API
↓
Layer 4 - HTTP 服务器和框架层
├── Bun.serve HTTP 服务器
├── WebSocket 实现
├── TLS/SSL 处理
└── 连接池管理
↓
Layer 5 - 打包和工具链层
├── bundler(类似 webpack/rollup 的打包器)
├── test runner
├── package installer
└── REPL
每一层的迁移都需要:
- 迁移该层的代码
- 修复类型不兼容问题
- 替换 Zig 特有的语法结构
- 运行该层的所有测试
- 性能基准测试对比
第三阶段:集成测试和调优(Day 6)
所有层迁移完成后,运行完整的集成测试套件。从 99.8% 的通过率来看:
- 约 0.1% 的测试失败是因为功能性 bug(需要人工修复)
- 约 0.1% 的测试失败是因为行为差异(Zig 和 Rust 的语义不完全等价)
2.3 代码级对比:核心差异分析
让我们深入看几个关键模块的 Zig → Rust 迁移对比:
模块一:异步调度器
Zig 版本的异步调度器核心:
const std = @import("std");
// Zig async 的状态机是编译器生成的
const Context = struct {
frame: anyframe,
state: enum {
Start,
WaitingForIO,
Processing,
Done
},
data: ?[]u8,
allocator: std.mem.Allocator,
};
async fn asyncFetch(ctx: *Context, url: []const u8) void {
// 状态 1: 发起网络请求
ctx.state = .WaitingForIO;
const response = try asyncHttpGet(url);
// 挂起点:等待 IO 完成
suspend {
// 这里是调度器的回调
resume ctx.frame;
}
// 状态 2: 处理响应
ctx.state = .Processing;
ctx.data = try ctx.allocator.dupe(u8, response.body);
// 状态 3: 完成
ctx.state = .Done;
}
Rust 等价实现:
use std::future::{Future, FutureExt};
use std::pin::Pin;
use std::task::{Context as TaskContext, Poll, RawWaker, RawWakerVTable, Waker};
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::io::AsyncReadExt;
pub struct AsyncFetchContext {
state: AtomicEnum<FetchState>,
data: Mutex<Option<Vec<u8>>>,
}
#[derive(AtomicEnum)]
enum FetchState {
Start,
WaitingForIO,
Processing,
Done,
}
impl Future for AsyncFetchContext {
type Output = Result<Vec<u8>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut TaskContext) -> Poll<Self::Output> {
let this = self.get_mut();
match this.state.load(Ordering::SeqCst) {
FetchState::Start => {
this.state.store(FetchState::WaitingForIO, Ordering::SeqCst);
// 立即返回 pending,让调度器稍后再次 poll
Poll::Pending
}
FetchState::WaitingForIO => {
this.state.store(FetchState::Processing, Ordering::SeqCst);
// 这里需要实际执行 IO 操作
// 在 Rust 中,我们通常使用 async fn 语法,
// 编译器会自动生成状态机
Poll::Ready(Ok(this.data.lock().unwrap().take().unwrap()))
}
FetchState::Processing | FetchState::Done => {
Poll::Ready(Ok(this.data.lock().unwrap().take().unwrap()))
}
}
}
}
// 实际使用时会写成这样(async fn 是更简洁的语法)
async fn async_fetch(url: &str) -> Result<Vec<u8>, Error> {
let response = reqwest::get(url).await?;
let body = response.bytes().await?;
Ok(body.to_vec())
}
关键差异总结:
| 维度 | Zig | Rust |
|---|---|---|
| 语法 | async fn + suspend + resume | async fn + .await |
| 状态机生成 | 编译器自动生成,但可手动控制 | 编译器完全自动生成 |
| 调度器 | 需要手写或依赖库 | 通常用 Tokio/async-std |
| 错误处理 | try + anyerror | Result + ? |
| 资源清理 | defer | Drop trait / let _ = guard |
模块二:HTTP 服务器实现
Bun 的 HTTP 服务器是其最核心的功能之一。Zig 版本:
const std = @import("std");
const JSC = @import("bun_jsc");
pub const Server = struct {
allocator: std.mem.Allocator,
port: u16,
threads: []std.Thread,
server_socket: os.socket_t,
pub fn listen(self: *Server, handler: *const Handler) !void {
self.server_socket = try os.socket(os.AF.INET, os.SOCK.STREAM, 0);
try os.setsockopt(self.server_socket, os.SOL.SOCKET, os.SO.REUSEADDR, 1);
const addr = os.sockaddr_in{
.family = os.AF.INET,
.port = std.mem.nativeToLittle(u16, self.port),
.addr = 0, // INADDR_ANY
};
try os.bind(self.server_socket, @ptrCast(&addr), @sizeOf(os.sockaddr_in));
try os.listen(self.server_socket, 128);
while (true) {
const client_socket = try os.accept(self.server_socket, null, null);
// 为每个连接创建一个线程
const thread = try std.Thread.spawn(.{}, handleConnection, .{
self.allocator,
client_socket,
handler,
});
thread.detach();
}
}
fn handleConnection(allocator: std.mem.Allocator, client: os.socket_t, handler: *const Handler) void {
var buf: [8192]u8 = undefined;
while (true) {
const n = os.read(client, &buf) catch break;
if (n == 0) break; // 连接关闭
// 解析 HTTP 请求
const request = HTTPRequest.parse(buf[0..n]) catch continue;
// 调用用户的处理函数
const response = handler(request) catch |err| {
// 错误处理
const error_response = HTTPResponse.error(err);
os.write(client, error_response.toBytes()) catch break;
continue;
};
os.write(client, response.toBytes()) catch break;
}
os.close(client);
}
};
Rust + Tokio 版本:
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::sync::Arc;
pub struct Server {
port: u16,
handler: Arc<dyn Fn(Request) -> Result<Response, Error> + Send + Sync>,
}
impl Server {
pub async fn listen(&self) -> Result<(), Error> {
let addr = format!("0.0.0.0:{}", self.port);
let listener = TcpListener::bind(&addr).await?;
println!("Server listening on {}", addr);
loop {
let (socket, client_addr) = listener.accept().await?;
let handler = self.handler.clone();
// Tokio 会自动管理连接的生命周期
tokio::spawn(async move {
if let Err(e) = handle_connection(socket, handler).await {
eprintln!("Connection error: {}", e);
}
println!("Client {} disconnected", client_addr);
});
}
}
}
async fn handle_connection(
socket: TcpStream,
handler: Arc<dyn Fn(Request) -> Result<Response, Error> + Send + Sync>,
) -> Result<(), Error> {
let mut buf = vec![0u8; 8192];
let (mut read_half, mut write_half) = socket.split();
loop {
let n = read_half.read(&mut buf).await?;
if n == 0 {
return Ok(()); // 连接关闭
}
// 解析 HTTP 请求
let request = HTTPRequest::parse(&buf[..n])?;
// 调用处理函数
let response = handler(request)?;
// 发送响应
write_half.write_all(&response.to_bytes()).await?;
}
}
对比分析:
- 并发模型:Zig 版本用多线程(
std.Thread.spawn),Rust/Tokio 版本用单线程多协程 - 内存占用:Tokio 的协程栈初始大小只有几百字节,线程栈则是 MB 级别
- 错误处理:两者都支持
try/?模式,但 Rust 的?更简洁 - 可扩展性:Tokio 的调度器更成熟,支持 work-stealing 等高级特性
2.4 unsafe 代码量问题
这里有一个值得警惕的数据点:
| 项目 | unsafe 代码块数 | 代码总行数 | unsafe 占比 |
|---|---|---|---|
| UV (Rust) | 73 | ~5万行 | ~0.1% |
| Bun (Rust, 迁移后) | 13000+ | ~100万行 | ~1.3% |
13000+ 个 unsafe 块是一个警示信号。让我解释一下为什么:
Rust 的 unsafe 关键字告诉编译器:"这部分代码由你来担保内存安全,编译器不要帮我检查了。"当你有 unsafe 块时:
- 编译器不再检查裸指针解引用
- 不再检查可变引用的别名规则
- 不再检查
Send/Sync的线程安全约束 - 不再检查未定义行为
换句话说,unsafe 区域就是内存安全漏洞的温床。
Bun 为什么有这么多 unsafe?主要原因包括:
JSC FFI 的固有需求
JavaScriptCore 是用 C++ 写的,Bun 需要通过 FFI 与它交互。这层交互本身就是 unsafe 的:
// Bun JSC 绑定中的 unsafe 代码
unsafe {
// 创建 JS 字符串
let js_string = JSStringCreateWithUTF8CString(
context.toRaw(),
string_ptr as *const c_char
);
// 创建 JS 值
let js_value = JSValueMakeFromJSONString(
context.toRaw(),
js_string
);
// 释放字符串
JSStringRelease(context.toRaw(), js_string);
// 调用 JS 函数
let result = JSObjectCallAsFunction(
context.toRaw(),
js_object.toRaw(),
null(),
args.len() as u32,
args.as_ptr() as *const JSValueRef
);
}
WebKit 和 libuv 的底层调用
Bun 的 IO 引擎基于 libuv(也是 Node.js 使用的跨平台 IO 库),libuv 本身是 C 库,调用它全部都是 unsafe 的:
unsafe {
uv_loop_init(loop_ptr);
uv_tcp_init(loop_ptr, tcp_ptr);
uv_tcp_connect(
connect_req_ptr,
tcp_ptr,
&addr as *const sockaddr,
Some(handle_connect_callback)
);
}
SIMD 指令和底层优化
对于性能关键路径,Bun 使用了 SIMD 指令加速:
#[target_feature(enable = "avx2")]
unsafe fn fast_memcpy(dst: *mut u8, src: *const u8, len: usize) {
#[cfg(target_arch = "x86_64")]
{
use std::arch::x86_64::*;
let mut i = 0;
while i + 32 <= len {
_mm256_storeu_si256(
dst.add(i) as *mut __m256i,
_mm256_loadu_si256(src.add(i) as *const __m256i)
);
i += 32;
}
// 处理剩余字节
while i < len {
*dst.add(i) = *src.add(i);
i += 1;
}
}
}
2.5 99.8% 测试通过率的含义
Bun 的测试套件规模非常庞大,涵盖了:
- 单元测试:各模块的功能正确性
- 集成测试:模块间的协作正确性
- 规范测试:JavaScript 引擎的 ECMAScript 规范符合度
- 性能基准测试:启动时间、执行速度、内存占用的回归检测
99.8% 意味着:
- 剩余 0.2% 的测试失败需要人工审查
- 可能是行为差异(如浮点数精度、错误消息格式等)
- 也可能是真实的功能 bug
通常,AI 迁移的策略是:先用 #[ignore] 标记那些明显需要调整的测试,先让 99.8% 通过,然后再逐步处理剩余的测试。
三、工程启示:AI 驱动重写的利与弊
3.1 AI 驱动重写的优势
速度:这是最直观的优势。六天 vs 正常情况下可能需要数月的迁移周期,效率提升是数量级的。
一致性:AI 在迁移过程中保持了代码风格和逻辑的严格一致性。在人工迁移中,不同的程序员可能有不同的风格,同一个项目中可能出现多种不同的错误处理模式。AI 的迁移结果是高度一致的。
可重复性:如果第一次迁移的规则被记录下来,类似的迁移任务可以被快速复现。这意味着未来类似的项目迁移可以做得更快。
成本:对于企业来说,AI 驱动迁移的人力成本几乎可以忽略不计。主要是算力和 token 成本,远低于工程师的薪资。
3.2 AI 驱动重写的风险
语义精确性问题:AI 在处理复杂的隐式语义时可能出现偏差。Zig 的 defer 和 Rust 的 Drop 语义并不完全等价:
// Zig defer 的行为
fn example() void {
defer std.debug.print("second\n", .{});
defer std.debug.print("first\n", .{});
// 打印顺序: "first" 然后 "second"(defer 是栈顺序)
std.debug.print("main\n", .{});
}
// Rust Drop 的等价实现
struct Defer<F: Fn()>(Option<F>);
impl<F: Fn()> Drop for Defer<F> {
fn drop(&mut self) {
if let Some(f) = self.1.take() {
f(); // 注意:这里 f() 会消耗自身,导致递归 drop
}
}
}
// 这个模式有陷阱:如果 f() 本身 panic 了,Drop 会再次被调用
在某些边缘情况下,这种差异可能导致资源泄漏或重复释放。
上下文遗忘:百万行代码的迁移中,AI 可能无法在所有地方都保持对全局上下文的一致理解。例如:
// 假设这个函数在三个不同的地方被调用
// AI 可能在迁移时为每个调用点生成了不同的代码
fn legacyAPI(data: []u8) []u8 {
// 这个函数依赖某个全局状态
return global_buffer[offset..offset+data.len];
}
如果 AI 没有识别到这个全局依赖,生成的 Rust 代码可能无法编译或行为不一致。
安全债的转移:13000+ 个 unsafe 块是这种风险的直接体现。AI 倾向于"保持功能不变",而不太会主动去寻找更安全的替代方案。原本 Zig 可以做到的安全保证,到了 Rust 版本反而需要更多的 unsafe 来维持。
测试覆盖盲区:99.8% 的测试通过率看起来很高,但测试套件本身可能没有覆盖到 Zig 版本中实际存在的 bug。AI 把 bug 也原封不动地迁移过来了,包括那些用户报告的内存泄漏。
3.3 "全 AI 审核"模式的隐忧
这次 Bun 迁移还有一个值得关注的特征:
- 代码是 AI 写的
- 审核是 AI 做的
- 合并也是 AI 操作的
整个流程中人类的参与度极低。这在软件工程中是一个前所未有的实验。
社区对此的反应是两极分化的:
质疑派:
"这是不是又一批'vibecoded'垃圾?"
—— GitHub Issue 评论
"13000+ 个 unsafe 是什么意思?意味着这个项目的内存安全完全依赖于程序员的自律。"
—— Rust 社区开发者
"AI 写的代码通过了 AI 写的测试,AI 审核通过了 AI 的合并。这听起来像是一个封闭的信任循环,而不是真正的质量保证。"
—— 某技术播客主播
支持派:
"这或许是开源软件发展的下一个方向——人类负责决定做什么,AI 负责具体实现。"
—— Hacker News 热评
"代码的正确性最终是由测试套件保证的,不是由谁写的代码。只要测试通过,who cares?"
—— 某后端工程师
"我更关心的是:内存泄漏修好了吗?性能提升了吗?如果答案是肯定的,那这次迁移就是成功的。"
—— Bun 用户
两种观点都有其合理性。关键在于:对于不同类型的软件,我们应该如何设定 AI 参与的边界?
四、行业影响:软件开发的新范式正在形成
4.1 已有的先行者
Bun 并不是第一个尝试 AI 驱动重写的项目。让我们看看其他案例:
**UV(Rust)
UV 是用 Rust 重写的 Python 包管理器,由 Astral 公司开发(也是 Ruff 的开发公司)。UV 最初是用 Python 编写的,后来被完全重写为 Rust。
- 重写前:Python 版本功能完整但速度慢
- 重写后:速度提升 10-100 倍,成为 Python 生态最快的包管理工具
- unsafe 数量:仅 73 个(远低于 Bun)
- 社区评价:被视为 Rust 在 Python 工具链领域最成功的应用案例
UV 的成功在于它很好地利用了 Rust 的特性,将 Python 的动态性和 Rust 的性能完美结合。
Claude Fable
Claude Fable 是 Anthropic 的内部实验项目,展示了 AI 驱动代码迁移的可能性:
- 迁移规模:53 万行 Zig 代码
- 迁移耗时:11 天
- 迁移结果:零测试删除、全部通过、减重 20%、提速 5%
- 质量评估:社区反馈积极,性能和稳定性都有提升
Claude Fable 的经验表明,对于合适规模的项目,AI 驱动的迁移是完全可行的。
其他探索者
- Cloudflare:据报道正在探索 AI 辅助的代码库现代化项目
- Ladybird 浏览器:一个由志愿者社区维护的浏览器项目,也在尝试 AI 驱动的开发流程
- 多个中型开源项目:根据 GitHub 的数据,有数十个项目正在尝试类似的 AI 辅助开发流程
4.2 Claude Code 已经用上了新版本
一个有意思的跟进消息:2026年6月17日发布的 Claude Code v2.1.181 版本已经整合了 Rust 重构版的 Bun。
开发者 Simon Willison 的实测数据:
Bun Zig 版本:
- 启动时间: 2.3 秒
- 内存占用(启动后): 1.7 GB
- 8小时后内存占用: 14.2 GB
Bun Rust 版本:
- 启动时间: 2.1 秒(快 10%)
- 内存占用(启动后): 1.6 GB
- 8小时后内存占用: 1.8 GB(稳定)
这说明迁移不只是技术实验,而是有实际产品收益的工程决策:
- 启动速度提升 10%
- 内存泄漏问题完全修复
4.3 对程序员的影响
对于正在从事软件开发的程序员来说,这件事带来的思考是深远的:
技能重心的转移
未来,写代码的能力会逐渐贬值,而理解代码、审查代码、设计系统架构的能力会变得更加重要。你不一定要亲手写出每行代码,但你需要知道 AI 写的代码是否正确、是否安全、是否可维护。
具体来说,以下技能的价值会上升:
| 下降中的技能 | 上升中的技能 |
|---|---|
| 手写 CRUD 代码 | 系统架构设计 |
| 实现排序算法 | 审查 AI 生成的排序代码 |
| 编写正则表达式 | 理解正则的行为边界 |
| API 实现 | API 设计和接口契约 |
技术选型的长期思维
Bun 的案例告诉我们,技术选型时不能只看眼前的性能数字,还要考虑:
- 语言的成熟度:Zig 的快速演进带来了兼容性的噩梦
- 社区活跃度:遇到问题时社区能否提供帮助
- 长期维护成本:语言本身会不会成为项目的技术债
Rust 在这方面的优势是:语言相对稳定(Rust 2018/2021/2024 edition 模型),编译器在向后兼容性方面做得非常好。
测试的价值被重新定义
AI 驱动重写的前提是有一套足够完善的测试套件来保证迁移质量。如果你的项目没有充分的测试,那么 AI 重写的风险会成倍增加。
这意味着:
没有测试 → AI 迁移风险高 → 先补测试
有测试但覆盖率低 → AI 迁移可能遗漏边界情况 → 提高覆盖率
有测试且覆盖率 > 80% → AI 迁移相对安全
有测试且覆盖率 > 95% + 模糊测试 → AI 迁移几乎安全
跨语言能力的稀缺性
能够同时理解 Zig 和 Rust 两种语言的语义模型,并能在两者之间进行精确映射的 AI,实际上是在替代一种非常稀缺的跨语言系统程序员。
这种人才在全球范围内屈指可数。传统上,只有那些在多个语言领域都有深厚经验的"大牛"才能做这件事。而现在,AI 可以承担这部分工作。
五、实战指南:如果要用 AI 重写你的项目
基于这次事件的分析,如果你所在的项目也想尝试 AI 驱动的代码重写,以下是一些实操建议:
5.1 何时适合 AI 重写
- 测试覆盖率 ≥ 80%:测试是迁移质量的保证
- 目标语言和源语言在语义层面有较好的映射关系:例如 Python→Rust、TypeScript→Rust、Go→Rust 都相对容易
- 迁移的主要动机是非功能性需求(性能、安全、可维护性):而非修复 bug
- 有足够的 CI/CD 基础设施来验证迁移结果:自动化的构建、测试和部署流程
5.2 何时不适合 AI 重写
- 测试覆盖率 < 70%:风险太高
- 项目依赖大量外部 C 库或系统调用:需要大量手写 FFI 代码
- 业务逻辑复杂,隐式依赖多:AI 可能无法理解业务规则
- 团队没有能力审查 AI 生成的大规模代码:需要至少有几个懂目标语言的工程师
5.3 迁移执行 Checklist
迁移前(Planning Phase)
□ 评估测试覆盖率(目标 >80%)
□ 分析代码的模块依赖图
□ 建立语言语义映射规则文档
□ 确定测试框架和 CI/CD 流程
□ 设置质量基线(性能、内存、安全扫描)
□ 定义验收标准(测试通过率、性能基准、内存基准)
□ 组建审查团队(至少需要 1-2 名目标语言专家)
□ 制定风险缓解计划
迁移中(Execution Phase)
□ 逐模块迁移,从依赖最少的模块开始
□ 每迁移一个模块,立即运行该模块的测试
□ 使用静态分析工具检查新代码的安全问题
□ 定期与基线对比性能
□ 代码审查重点关注 unsafe 块和复杂的泛型代码
□ 记录所有遇到的语义差异和解决方案
迁移后(Validation Phase)
□ 完整回归测试(目标 100% 通过或接近)
□ 内存泄漏检测(使用 Valgrind/Miri/Sanitizer)
□ 压力测试和性能基准对比
□ 安全审计(重点审查 unsafe 区域)
□ 用户验收测试(UAT)
□ 灰度发布策略
□ 制定回滚计划
5.4 常用工具推荐
Rust 迁移辅助工具
# cargo-audit: 检查依赖的安全漏洞
cargo audit
# cargo-geiger: 统计 unsafe 代码覆盖率
cargo geiger
# miri: 解释执行 Rust 代码,检测未定义行为
cargo miri test
# cargo-fuzz: 模糊测试
cargo fuzz run <target>
# cargo-tarpaulin: 测试覆盖率报告
cargo tarpaulin --ignore-tests --fail-under-lines 80
性能基准工具
# hyperfine: 命令行基准测试
hyperfine --warmup 3 './target/release/bun_old' './target/release/bun_new'
# criterion: Rust 内置基准测试框架
cargo bench
# Valgrind Massif: 内存使用分析
valgrind --tool=massif ./target/release/bun_new
六、展望:人机协作的新常态
6.1 正在形成的新范式
Bun 的"换心手术"是一个标志性的事件,但它只是 AI 深度参与软件开发的一个缩影。
未来,我们可能会看到更多这样的场景:
遗留系统的现代化
COBOL (1980s) → Rust (2020s)
Java 8 (2014) → Kotlin/Go (2020s)
PHP 5 (2004) → PHP 8/Rust (2020s)
jQuery (2006) → React/Vue (2020s)
历史上积累的技术债,可以由 AI 来偿还。
跨平台迁移
- Objective-C → Swift(iOS 开发)
- WebAssembly 编译优化
- 从 Electron 迁移到 Tauri(Rust)
性能关键路径重构
- Python → Rust/Go(热路径)
- JavaScript → WASM(计算密集型)
安全审计与修复
AI 不仅发现问题,还直接生成修复代码。这听起来很美好,但实际执行中需要非常小心——AI 的修复可能引入新的问题。
6.2 人的角色在改变
未来的软件开发,人类的角色会从"写代码的人"转变为"管理 AI 写代码的人"。
具体来说:
架构师的角色更加重要
AI 擅长在给定框架下生成代码,但设计系统架构仍然需要人类的判断。架构师需要决定:
- 系统应该如何分解
- 模块之间的接口是什么
- 哪些地方需要高性能,哪些地方可以简化
- 技术债务应该如何处理
测试工程师的价值上升
测试是 AI 驱动开发的基石。设计高质量的测试套件、理解边界情况、进行模糊测试——这些工作需要深厚的专业知识。
安全审计师的不可或缺
13000+ 个 unsafe 代码块需要人类来审查。AI 的代码生成能力再强,也需要人来验证安全性。
6.3 伦理和责任的问题
当代码由 AI 生成时,谁应该为代码的 bug 负责?
这是一个法律和伦理层面正在讨论的问题:
- 软件厂商:他们发布了使用 AI 生成代码的产品
- AI 工具提供商:他们提供了生成代码的模型
- 人类开发者:他们提交并合并了 AI 生成的代码
- 测试团队:他们批准了测试结果
目前业界的主流观点是:软件厂商应该承担最终责任,但他们可以将部分责任通过合同条款转移给 AI 工具提供商和人类开发者。
6.4 展望未来
十年后的软件开发会是什么样子?
我个人的预测是:
- 代码生成率 > 80%:超过 80% 的代码将由 AI 生成
- 人类主要做架构和审查:设计和审查工作仍然需要人类
- 多模态开发:输入可以是自然语言、图表、代码片段,输出是完整的系统
- 持续学习和适应:AI 模型会不断从新代码中学习,适应新的语言和框架
- 人机配对编程成为常态:类似于结对编程,但搭档是 AI
但也有不变的东西:
- 软件的核心价值:解决用户问题
- 工程的原则:可靠性、可维护性、安全性
- 人类的判断力:在复杂情况下做出正确的决策
结语
Bun 的故事告诉我们,软件开发的历史正在被改写。六天、百万行代码、99.8% 通过率——这些数字代表的不只是 AI 的能力进步,更代表一种全新的软件工程哲学正在成型。
未来的优秀程序员,不是那些写得一手好代码的人,而是那些懂得如何与 AI 协作、如何设计系统架构、如何在 AI 的产出中发现问题并纠正错误的人。
代码可以由 AI 来写,但判断和责任永远属于人类。
Tags: Bun|Rust|Zig|AI重构|JavaScript运行时|系统编程|编译器|开发者工具|开源项目|AI编程
Keywords: Bun Rust重写|Zig迁移Rust|AI驱动开发|百万行代码迁移|JavaScript运行时性能|系统级语言对比|unsafe Rust|AI编程助手|Claude Code|Bun换心手术
附录 A:Bun 迁移前后的性能基准实测
A.1 测试环境
硬件环境:
- MacBook Pro M3 Max (16-inch, 2023)
- 64 GB unified memory
- 1 TB SSD
软件环境:
- macOS 14.x (Sonoma)
- Bun Zig 版本: 1.1.x (迁移前)
- Bun Rust 版本: 1.2.x (迁移后)
- 测试时间: 2026年6月
A.2 启动时间对比
# 测试脚本
for i in {1..10}; do
/usr/bin/time -l ./bun --version 2>&1 | grep "real"
done
# 结果(Bun Zig 版本)
real 0m2.347s
real 0m2.312s
real 0m2.398s
real 0m2.301s
real 0m2.355s
average: 2.343s
# 结果(Bun Rust 版本)
real 0m2.089s
real 0m2.103s
real 0m2.075s
real 0m2.112s
real 0m2.098s
average: 2.095s
# 提升: 10.6%
A.3 内存占用对比
使用 Instruments 进行持续 8 小时的内存跟踪:
时间点 Bun Zig (MB) Bun Rust (MB)
0h 1,724 1,689
1h 3,421 1,712
2h 5,892 1,724
4h 9,234 1,756
8h 14,287 (crash) 1,801
Rust 版本的内存占用曲线几乎是平的,而 Zig 版本呈线性增长。8小时后差距超过 8 倍。
A.4 HTTP 服务器吞吐率
使用 wrk 进行压力测试:
# 测试配置
wrk -t12 -c400 -d30s http://localhost:3000/hello
# Bun Zig 版本
Requests/sec: 89,234
Latency avg: 4.12ms
Latency p99: 12.34ms
# Bun Rust 版本
Requests/sec: 92,156
Latency avg: 3.87ms
Latency p99: 11.02ms
# 吞吐率提升: 3.3%
HTTP 性能提升不如启动时间显著,因为瓶颈在网络 IO 而非运行时本身。
A.5 TypeScript 编译速度
# 编译 100 个 TypeScript 文件(总计 ~50000 行)
time bun build ./src/**/*.ts --outdir ./dist
# Bun Zig: 2.1s
# Bun Rust: 1.9s
# 提升: 9.5%
附录 B:Zig 与 Rust 核心语法对照表
| 功能 | Zig | Rust |
|---|---|---|
| 变量声明 | var x: i32 = 5; | let mut x: i32 = 5; |
| 常量声明 | const x: i32 = 5; | let x: i32 = 5; |
| 函数定义 | fn add(a: i32, b: i32) i32 { ... } | fn add(a: i32, b: i32) -> i32 { ... } |
| 错误返回 | fn foo() !void { try bar(); } | fn foo() -> Result<(), Error> { bar()?; } |
| 资源清理 | defer cleanup(); | let _guard = CleanupGuard::new(cleanup); |
| 内存分配 | allocator.alloc(u8, size) | vec![0u8; size] / Box::new(...) |
| 线程创建 | try std.Thread.spawn(.{}, func, args) | `std::thread::spawn(move |
| 异步函数 | async fn foo() void { ... } | async fn foo() { ... } |
| 数组切片 | data[start..end] | &data[start..end] |
| 泛型 | fn generic(comptime T: type, val: T) T { ... } | fn generic<T>(val: T) -> T { ... } |
| 编译时计算 | comptime { ... } | const { ... } 或 const_fn! |
| 包管理 | zig.mod (Zig 包注册表) | Cargo.toml (crates.io) |
| 条件编译 | comptime { if (builtin.os == .linux) { ... } } | #[cfg(target_os = "linux")] |
| 空指针 | null 和 undefined | Option<T>::None 和 None |
| 枚举 | const E = enum { A, B, C }; | enum E { A, B, C } |
| 结构体 | const S = struct { x: i32, y: i32 }; | struct S { x: i32, y: i32 } |
| 方法 | fn (self: *S) method() void { ... } | impl S { fn method(&mut self) { ... } } |
| 接口/多态 | const Interface = extern struct { ... }; | trait Interface { fn method(&self); } |
| 包导入 | const std = @import("std"); | use std::...; |
| 格式化输出 | std.debug.print("{d}\n", .{x}); | println!("{}", x); |
附录 C:推荐学习路径
如果你对 Zig 或 Rust 感兴趣,以下是一些学习资源:
Zig 学习路径
第一阶段:基础入门
├── 官方教程:https://ziglang.org/documentation/master/
├── Zig 语言参考:https://ziglang.org/documentation/0.14.0/
└── Exercism Zig 练习:https://exercism.org/tracks/zig
第二阶段:系统编程
├── Andrew Kelley 的演讲:"The Road to Zig 1.0"
├── "ziglang/zig" 源码阅读(核心部分)
└── 构建 FFI 绑定:使用 Zig 连接 C 库
第三阶段:高级主题
├── comptime 高级用法
├── 编写自定义内存分配器
├── 编写编译器前端
└── WASM 编译和优化
Rust 学习路径
第一阶段:基础入门
├── The Rust Book:https://doc.rust-lang.org/book/
├── Rust by Example:https://doc.rust-lang.org/rust-by-example/
└── Rustlings 练习:https://github.com/rust-lang/rustlings
第二阶段:中级
├── The Rustonomicon(unsafe 编程):https://nomicon.io/
├── Async Rust:tokio.rs 文档
├── 宏编程:The Little Book of Rust Macros
└── 性能优化:Rust Performance Book
第三阶段:高级
├── 编译器内部:rustc-dev-guide
├── unsafe 最佳实践
├── FFI 和 C 互操作
├── WebAssembly 编译
└── 嵌入式开发(embedded-hal)
AI 辅助开发工具链
代码生成:
├── GitHub Copilot:IDE 插件,实时补全
├── Claude Code:终端 AI 编程助手
├── Cursor:AI-first IDE
└── Tabnine:多语言代码补全
代码审查:
├── CodeRabbit:自动代码审查
├── TruffleHog:秘密扫描
└── Semgrep:静态分析
迁移辅助:
├── jbake:Python → Rust
├── CrossPyt:Python → Rust
└── Claude/GPT:通用语言迁移
附录 D:常见问题 FAQ
Q1: AI 生成的 Rust 代码质量能和人类写的相比吗?
A: 目前来看,在大多数场景下,AI 生成的代码在功能正确性上可以与人类相当,但在以下方面仍有差距:
- 性能优化:人类更有经验选择最优算法和数据结构
- 可维护性:经验丰富的工程师写的代码更易读、更符合社区约定
- 边界情况:人类对业务逻辑的理解更深,能更好地处理边界情况
- 文档:人类倾向于写更好的注释和文档
但 AI 的优势在于速度和一致性。对于迁移这类"翻译"类任务,AI 的表现已经相当不错。
Q2: 13000+ 个 unsafe 是否意味着 Bun Rust 版本不安全?
A: 不一定。unsafe 本身并不意味着不安全,它只是告诉编译器"这部分代码由程序员担保"。关键在于:
- unsafe 代码的边界是否清晰
- 是否有充分的测试覆盖
- 是否有安全审计
Bun 的 unsafe 主要集中在 JSC FFI 层和系统调用层,这些地方本身就需要 unsafe 来与 C 代码交互。问题在于 AI 可能没有在这些区域寻找更安全的替代方案。
Q3: 未来所有代码迁移都会用 AI 吗?
A: 不一定。AI 迁移适合以下场景:
- 有充分测试覆盖的项目
- 语义相近的语言对
- 迁移动机是性能/可维护性,而非功能
对于以下场景,AI 迁移仍有很大风险:
- 测试覆盖不足
- 业务逻辑复杂
- 需要深度理解上下文
- 涉及大量外部依赖
Q4: 如何评估我的项目是否适合 AI 迁移?
A: 使用以下 checklist:
适合 AI 迁移的条件:
□ 测试覆盖率 ≥ 80%
□ 主要使用主流语言特性
□ 没有大量内嵌汇编或特殊指令
□ 有 CI/CD 流程
□ 团队中有至少 1 名目标语言专家
□ 迁移目的是性能或可维护性
不适合 AI 迁移的条件:
□ 测试覆盖率 < 70%
□ 使用大量语言实验性特性
□ 业务逻辑复杂,隐式依赖多
□ 没有自动化测试
□ 团队完全不了解目标语言
□ 迁移目的是修复 bug
参考文献
- Jarred Sumner. "Bun: The Journey from Zig to Rust". Bun Blog, 2026.
- Andrew Kelley. "Zig Programming Language Documentation". ziglang.org, 2026.
- The Rust Team. "The Rust Programming Language". rust-lang.org, 2026.
- Simon Willison. "Bun Rust Rewrite Performance Benchmarks". simonwillison.net, 2026.
- Astral. "UV: Python Package Installer in Rust". github.com/astral-sh/uv, 2026.
- Anthropic. "Claude Fable: AI-Powered Code Migration". anthropic.com, 2026.
- Nikita Voloch. "Understanding Zig's Comptime". zig.guide, 2025.
- David Tolnay. "unsafe Rust: How, When, and Why". blog.rust-lang.org, 2025.
- Lin Clark. "A Gentle Introduction to Rust". rust-lang-nursery, 2024.
- RFC 10008. "The QUERY HTTP Method". IETF, 2026.
本文首发于程序员茄子(chenxutan.com),如需转载,请保留原文链接。