Bun 用 Claude 在 6 天内重写 96 万行 Rust:AI 驱动的大规模代码迁移深度解析——从 Zig 到 Rust 的工程实践、unsafe 争议与信任危机
一、一场改写开源软件历史的实验
2026 年 5 月,JavaScript 运行时 Bun 完成了一次史无前例的技术跨越:用 AI 在大约 6 天内将整个核心运行时从 Zig 语言迁移到 Rust,涉及约 96 万行代码、6755 个 commit,现有测试套件通过率 99.8%。这不是概念验证,不是 demo,而是一个拥有 9 万多 GitHub star、被 Claude Code 等生产级工具深度依赖的运行时,直接把 AI 生成的 Rust 代码合入了主分支。
这件事为什么值得每个程序员关注?因为它不只是 Bun 的技术选型问题,更是 AI 重写软件这个范式的一次公开大考。当 AI 可以用 6 天完成人类可能需要数年的跨语言迁移时,速度、质量、信任三者之间的张力,就成了我们必须正视的工程命题。
二、背景:为什么 Bun 要离开 Zig?
2.1 四年 Zig 之路:从明星项目到困境
Bun 自诞生以来,一直是 Zig 生态最成功的代表作。Zig 带来了极致的启动速度、对系统底层的精细控制,以及与 C 的无缝互操作能力。Bun 凭借这些优势,一度成为 Node.js 和 Deno 之外最令人期待的 JavaScript 运行时。
但四年下来,问题开始积累:
内存泄漏成为顽疾。 Bun 的内存问题不是偶发的小 bug,而是系统性缺陷。2026 年 3 月,Claude Code 的 GitHub 仓库收到一个编号 #33453 的 Issue:主进程在约 3 小时的会话中,RSS 内存从 1.7GB 暴涨到 14GB。Issue 作者直接指出泄漏位于 Bun 运行时的 WebKit Malloc 分配器,而非用户空间的 JavaScript 分配。另一份 Issue #11377 更夸张:运行 14 小时后,Claude Code 进程占用 23GB 虚拟内存,143.8% CPU,系统完全卡死。
Issue 积压触目惊心。 波兰数字会员系统公司 Rewardo 的 CTO Wojciech Maj 做过一个对比:Node.js 作为几乎"驱动整个互联网"的运行时,大约有 1700 个 open issues;而用户规模远小于 Node.js 的 Bun,却已经积累了约 4700 个 open issues。这个差距直接反映了项目成熟度和维护能力的差距。
路线图偏移。 Reddit 用户 Xtergo 的吐槽很有代表性:"Bun 的路线图看起来更像是在不断叠加新功能,而不是优先解决稳定性和 Bug 修复问题。如果这些问题继续得不到解决,我怀疑它永远无法达到 Node.js 那种生产级成熟度。"
2.2 Zig 社区的哲学冲突
内存问题只是表面,更深层的裂痕来自 Bun 和 Zig 社区之间的哲学冲突。
Bun 团队此前已经 fork 了 Zig,声称通过引入 LLVM 并行代码生成,debug 编译速度提升了四倍。但这些优化始终无法 upstream 回 Zig 官方。原因很明确:Zig 社区实行极其严格的"no-AI policy"——禁止 AI 生成的 issue、PR 甚至评论。Zig 基金会成员 Loris Cro 公开表示,大量 LLM 贡献只会制造"幻觉 PR"和"垃圾噪音"。
与此同时,Zig 核心开发者批评 Bun fork 中的一些实现"不适合 upstream",例如并行语义分析可能导致非确定性行为,Bun 对 LLVM backend 的模块拆分方向也被认为有误。
这种冲突在 Anthropic 收购 Bun 后显得格外讽刺:Anthropic 是 AI coding 浪潮最激进的推动者之一,Claude Code 又深度依赖 Bun runtime。一边是 Zig 社区全面封禁 AI 生成代码,另一边是 Bun 团队用 Claude agent 大规模把 Zig 迁移出去。这已经不是语言选型问题,而是两种软件工程哲学的正面碰撞。
2.3 Anthropic 收购后的压力
2025 年 12 月,Anthropic 收购了 Bun。官方说法是"加速 Claude Code 能力",本质上 Bun 成了 Claude Code 背后的运行时、包管理器、bundler 和测试工具。
Claude Code 负责人 Boris Cherney 曾解释选择 Bun 的原因:
"Bun 的启动时间大概只有 3 毫秒,而 Python 要慢 15 倍左右。对于 CLI 工具来说,这意味着用户体验是'丝滑响应',还是'明显卡顿'。"
但现实是:Claude Code 以 Bun 可执行文件的形式发布,Bun 的内存泄漏直接成了 Claude Code 的内存泄漏。一个荒诞的循环形成了——Claude Code 被 Bun 的内存泄漏坑惨了,然后 Anthropic 让 Claude 去重写 Bun,Bun 再继续支撑 Claude Code。
三、迁移过程:576 行指南与六天冲刺
3.1 PORTING.md:AI 大规模迁移的工程规范
2026 年 5 月初,Bun 仓库出现了一个名为 claude/phase-a-port 的新分支,同时出现了一份长达 576 行的 PORTING.md 文档。这份文档才是整个迁移的真正灵魂——它不是让 AI 自由发挥,而是极其精细地规定了迁移的每一步:
Phase A:逐文件忠实翻译
- 逐文件将 Zig 代码翻译为 Rust,忠实保留原始逻辑
- 即使 Rust 代码暂时不能编译也没关系
- 文件命名规则:
src/bun.js/xxx.zig→crates/bun-js-xxx/src/xxx.rs - Crate 引用必须严格按模块边界划分
Phase B:逐 crate 修复编译
- 逐个 crate 解决编译、构建和运行问题
- 这个阶段才开始处理 Rust 特有的所有权、生命周期问题
硬性约束:
- 禁止使用 tokio、rayon、hyper、futures 等 async 生态库
- 禁止使用 async fn
- unsafe 必须写明 SAFETY 注释,解释为什么安全
- 遇到不确定逻辑时,宁可留下 TODO,也不要让 AI 自行猜测
这种设计非常聪明:先让 AI 做"语义投影"——把 Zig 的逻辑 1:1 投射到 Rust,不追求 Rust idiom,只求行为一致。然后再逐步优化。这避免了 AI 在迁移过程中"创造性发挥"带来的风险。
3.2 时间线:从"可能扔掉"到"合并入主分支"
| 日期 | 事件 |
|---|---|
| 5月5日 | claude/phase-a-port 分支出现,PORTING.md 发布 |
| 5月7日 | Jarred 发推:4000 次 commit、96 万行代码,只剩 3 个编译错误 |
| 5月7日 | Rust 版本已能运行 JavaScript,bun run 和 package.json scripts 可用 |
| 5月9日 | Linux x64 glibc 环境下测试套件通过 99.8% |
| 5月11日 | Jarred 发推:"如果我们合并 Rust 重写版本,这将是 Zig 的最后一个版本" |
| 5月14日 | PR #30412 合并入 main:Rewrite Bun in Rust |
注意 5 月 7 日 Jarred 在 Hacker News 上的说法:
"整个讨论有点反应过度了。302 条评论,全都围绕一堆根本还跑不起来的代码。我们并没有决定一定要重写。而且这些代码最后被全部扔掉的概率其实非常高。"
六天后,同样的代码变成了"Zig 的最后一个版本"。从"大概率扔掉"到"合并入主分支",只用了不到一周。
3.3 迁移的工作流设计
Jarred Sumner 使用的是 Claude Code 的动态工作流模式,而非简单的对话式编程。具体操作方式是:一个工作流先把 Zig 代码逐文件翻译成 Rust,另一个工作流再逐个 crate 修复编译问题。这种 pipeline 式的自动化流程,让 AI 可以持续不间断地工作,不需要人类在每个步骤之间手动介入。
在 5 月 3 日,Jarred 就曾在推特上预告过:
"这种 pipeline,任何 VC 支持的 OSS 或者有大量 GitHub issues 的公司都能搭建。更普遍地说,它可以用于自动修复用户报告的 bug。"
他还更早预言过:
"我预计开源软件会走向完全相反的方向——未来甚至可能变成'禁止人类贡献代码'。人类依然会负责讨论问题、决定优先级,但真正写代码、提交 PR、回复和处理反馈、完成实现的工作,最终都会由 LLM 来完成。"
四、unsafe 争议:13000 个安全漏洞还是必要的底层接口?
4.1 73 vs 13000:一组刺眼的数字
迁移公布后,最引爆争议的对比来自 t3.gg 创始人 Theo:
- uv(Astral 出品的 Python 包管理器):35 万行 Rust 代码,73 个 unsafe 调用
- Bun Rust 移植版:68.1 万行 Rust 代码,超过 13000 个 unsafe 调用
差了接近 180 倍。
Jarred 几乎立刻回应:"今天已经下降了大约 2000。我预计它会稳定在 1 万左右,因为 Bun 的大部分内容都是用 C 和 C++ 编写的,这种情况不会改变。"
4.2 这个对比公平吗?
平心而论,这个对比确实不完全公平。uv 是一个相对纯粹的 Rust 项目,而 Bun 需要与大量底层 C/C++ 组件打交道:
// Bun 必须通过 unsafe 与 JavaScriptCore 交互
unsafe {
jsc_context_evaluate(context, script_ptr, script_len)
}
// Bun 必须通过 unsafe 调用 libuv 的 C API
unsafe {
uv_fs_open(loop_ptr, req_ptr, path_ptr, flags, mode, callback)
}
// Bun 必须通过 unsafe 操作 mimalloc 分配器
unsafe {
mi_malloc_aligned(size, alignment)
}
JavaScriptCore、libuv、mimalloc、uWebSockets——这些都是 C/C++ 写的,Bun 必须通过 FFI(Foreign Function Interface)与它们交互,而 FFI 在 Rust 中天然就是 unsafe 的。
4.3 Bun 官方的 unsafe 审计
5 月 21 日,Bun 官方发布了 unsafe 审计页面,确认了以下数据:
- Rust port 有 13,365 个 unsafe blocks
- 约 9,300 个可以转成 safe code
- 约 4,000 个会保留在必要边界上
官方解释 unsafe 的三大来源:
- FFI 边界:与 JavaScriptCore、libuv 等 C/C++ 组件的接口调用
- 从 Zig port 过来的 ownership idiom:Zig 的内存管理方式直接映射到 Rust 时,很多地方需要 unsafe 来模拟 Zig 的指针操作
- 少量性能路径:热路径上为避免 Rust 的运行时检查而使用 unsafe
4.4 真正的问题不在数量,在可审查性
开发者 Aashish Ranjan Singh 的评论直击要害:
"UV rust 是由真正的开发人员编写的,每一行代码都经过了审查。Bun rust 由 Agents 编写,由 Agents 审核,并由 Agents 批准和合并。"
这才是核心问题。如果每个 unsafe block 都有人类工程师审查过、写过 SAFETY 注释、验证过不变量,那 13000 个也许是合理的。但如果这些 unsafe 是 AI 在"忠实翻译"过程中批量生成的,没有人逐个验证过安全性条件是否真正成立,那每一个 unsafe 都可能是一个定时炸弹。
让我展示一个具体的例子,说明 AI 翻译可能产生的 unsafe 隐患:
// Zig 原始代码:直接指针操作,但 Zig 的安全性保证不同
fn getObject(ptr: [*]const u8, offset: usize) *const Object {
return @ptrFromInt(@intFromPtr(ptr) + offset);
}
// AI 翻译的 Rust 代码:直接复制了 Zig 的指针逻辑
unsafe fn get_object(ptr: *const u8, offset: usize) -> *const Object {
// SAFETY: ??? (AI 可能没有验证对齐、非空、生命周期)
(ptr as *const u8).add(offset) as *const Object
}
Zig 中 @ptrFromInt 是有一定安全保障的(比如 debug 模式下会检查对齐),但 Rust 的裸指针强制转换没有这些检查。如果 AI 只是"忠实翻译"了逻辑而没有理解两种语言在安全性语义上的差异,SAFETY 注释可能就是空的或者不正确的。
4.5 unsafe 审计的正确做法
对 AI 大规模生成的代码做 unsafe 审计,应该包含以下步骤:
第一步:分类标记
// Category 1: FFI boundary - 与 C/C++ 交互(必要的 unsafe)
unsafe fn jsc_evaluate(ctx: *mut JSCContext, script: *const c_char) -> JSValueRef {
// SAFETY: ctx 由 JSCContext::new 保证有效,script 由调用者保证 \0 结尾
jsc_context_evaluate(ctx, script, strlen(script))
}
// Category 2: Zig idiom translation - 从 Zig 习惯搬运过来的(可以改为 safe)
unsafe fn get_task_data(task: *mut Task) -> &TaskData {
// SAFETY: Zig 用 tagged pointer,Rust 可以用 enum
// TODO: 重构为 enum + Option 消除此 unsafe
&*((*task).data as *const TaskData)
}
// Category 3: Performance path - 性能优化(需要 benchmark 验证)
unsafe fn fast_hash(data: &[u8]) -> u64 {
// SAFETY: data 保证有效且对齐,read_unaligned 在对齐时等价于 read
// Benchmark: 此路径占热点循环 15%,使用 safe 版本性能下降 8%
data.as_ptr().read_unaligned() as u64
}
第二步:逐个验证 SAFETY 注释
每个 unsafe block 的 SAFETY 注释必须回答:
- 哪些不变量(invariant)是调用者必须保证的?
- 这些不变量在当前调用上下文中是否真的成立?
- 如果不变量被违反,后果是什么?是否会导致未定义行为?
第三步:消除可消除的 unsafe
Bun 官方说 9300 个可以转成 safe。但"可以"不等于"会"。这需要一个系统性的重构计划:
// Before: AI 翻译的 unsafe 代码
unsafe fn handle_event(event: *mut Event) -> EventResult {
let kind = (*event).kind;
let data = (*event).data;
process_event(kind, data)
}
// After: 重构为 safe Rust
fn handle_event(event: &Event) -> EventResult {
process_event(event.kind, event.data)
}
五、社区反应:信任危机与项目出走
5.1 yt-dlp:第一个正式限制 Bun 的项目
5 月 20 日,视频下载工具 yt-dlp 在 GitHub issue #16766 中宣布:Bun 作为 EJS 兼容 JavaScript runtime 的支持将被限制并弃用。它把支持范围限定到 Bun 1.2.11 到 1.3.14(Zig 版本的最后版本)。
理由有两层:
- 早期 Bun 版本构建 EJS 时可能忽略 lockfile,结合近期 npm 供应链攻击背景被认为有安全风险
- Bun 最近用 Claude 重写成 Rust,维护者认为其开发方向有"fully vibe-coded"的趋势
5.2 Electrobun:架构核心解耦
Electrobun 原本把 Bun 放在架构核心——官方文档写着"Electrobun app 本质上是一个 Bun app"。5 月 23 日,作者 Yoav 宣布 Electrobun 2.0 将因 Rust rewrite 与 Bun 解耦。
这是一个非常重大的信号:当你的架构核心组件的底层实现被 AI 在 6 天内重写,你不知道下个版本会不会又来一次"换心手术",这种不确定性对下游项目来说是致命的。
5.3 其他迁移动态
| 项目 | 动作 | 时间 | 与 Rust 重写的关系 |
|---|---|---|---|
| Sentry CLI | 移除 Bun artifacts,迁移到 Node.js | 5月22日 | 未明确提及 |
| OpenTUI | 移除 Bun-specific runtime dependencies | 5月6日 | 发生在合并前 |
| Variableland/dx | 从 Bun runtime 迁到 Node.js | 5月1日 | 发生在合并前 |
准确说法:至少 yt-dlp 和 Electrobun 的解耦是直接回应 Rust 重写事件;其他项目更多是 runtime-agnostic 的趋势,不能都归因于同一事件。
六、技术深度:Zig 到 Rust 迁移的工程挑战
6.1 内存模型差异
Zig 和 Rust 的内存管理哲学完全不同,这是迁移中最核心的挑战。
Zig 的方式:手动管理 + 编译器检查
const std = @import("std");
fn processBuffer(allocator: std.mem.Allocator) ![]u8 {
// 显式分配,调用者负责释放
var buf = try allocator.alloc(u8, 1024);
// Zig 没有 borrow checker,但编译器会在 debug 模式检测一些问题
return buf;
}
Rust 的方式:所有权 + 借用检查
fn process_buffer() -> Vec<u8> {
// 所有权自动管理,编译器保证内存安全
let mut buf = vec![0u8; 1024];
buf
}
当 AI "忠实翻译" Zig 代码时,最常见的做法是:
// AI 翻译:用 unsafe 绕过 borrow checker
unsafe fn process_buffer(allocator: &mut Allocator) -> *mut u8 {
let buf = allocator.alloc(1024);
// 直接返回裸指针,跳过所有权检查
buf
}
这就是为什么会有 13000 个 unsafe——因为 AI 在翻译时选择了最保守的策略:保持 Zig 的指针语义,用 unsafe 绕过 Rust 的安全检查。这在短期内可以让代码"跑起来",但长期来看,这些 unsafe 块就是技术债。
6.2 Tagged Pointer 的困境
Bun 大量使用 tagged pointer 来处理 event loop task、进程退出回调、非阻塞文件 I/O 等接口。Jarred 在 X 上专门请教 Rust 社区:
"Bun 原来的 Zig 代码大量使用 tagged pointer 来处理 event loop task、进程退出回调、非阻塞文件 I/O 等接口;迁到 Rust 后,如果直接用 trait 或函数指针,可能会带来额外开销。"
Tagged pointer 在 Zig 中的使用:
// Zig: 用指针低位存储类型标记
const Task = struct {
tagged_ptr: usize, // 低位 2 bits 作为类型标记
fn getType(self: *const Task) TaskType {
return @enumFromInt(self.tagged_ptr & 0x3);
}
fn getData(self: *const Task) *align(1) TaskData {
return @ptrFromInt(self.tagged_ptr & ~@as(usize, 0x3));
}
};
Rust 中的安全替代方案:
// 方案1: 用 enum 替代 tagged pointer(安全但可能有额外开销)
enum Task {
FileRead(Arc<FileReadData>),
Timer(Arc<TimerData>),
Callback(Arc<CallbackData>),
}
// 方案2: 使用 NonNull + 标记位(仍然是 unsafe,但更规范)
use std::ptr::NonNull;
struct TaggedPtr(NonNull<TaskData>);
impl TaggedPtr {
// SAFETY: 调用者保证 ptr 非空且对齐到 4 字节
unsafe fn new(ptr: NonNull<TaskData>, tag: u8) -> Self {
let addr = ptr.as_ptr() as usize;
debug_assert_eq!(addr & 0x3, 0, "pointer must be 4-byte aligned");
TaggedPtr(NonNull::new_unchecked((addr | (tag as usize)) as *mut TaskData))
}
fn tag(&self) -> u8 {
(self.0.as_ptr() as usize & 0x3) as u8
}
fn data(&self) -> NonNull<TaskData> {
unsafe {
let addr = self.0.as_ptr() as usize & !0x3;
NonNull::new_unchecked(addr as *mut TaskData)
}
}
}
方案 1 更安全,但有内存布局开销(enum discriminant + padding);方案 2 保持了 Zig 的性能特征,但需要 unsafe。在性能敏感的 runtime 底层,通常选择方案 2,但必须严格验证 SAFETY 条件。
6.3 错误处理模型差异
Zig 使用 error union,Rust 使用 Result。表面相似,但语义不同:
// Zig: error union 是值类型,零开销
fn readFile(path: []const u8) ![]u8 {
const file = try std.fs.cwd().openFile(path, .{});
defer file.close();
return try file.readToEndAlloc(allocator, max_size);
}
// Rust: Result 有运行时开销(但通常很小)
fn read_file(path: &str) -> Result<Vec<u8>, std::io::Error> {
let mut file = std::fs::File::open(path)?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
Ok(buf)
}
AI 在翻译时,经常直接把 try 映射为 ?,把 ! 映射为 Result<_, Box<dyn Error>>。但 Zig 的 error union 是穷举的——编译器知道所有可能的错误类型;Rust 的 Box<dyn Error> 是类型擦除的——你丢失了错误类型的编译时信息。
6.4 构建系统迁移
Bun 原来使用 Zig 的构建系统,迁移到 Rust 后需要使用 Cargo。这带来几个挑战:
- 依赖管理:Zig 没有包管理器,Bun 手动管理所有 C/C++ 依赖;Cargo 有 crates.io,但需要为每个 C/C++ 依赖编写
-syscrate - 交叉编译:Zig 的交叉编译开箱即用,Rust 需要配置 target 和 linker
- 构建速度:Rust 的编译速度是出了名的慢,这对开发体验影响很大
七、性能对比:Rust 版本真的更快吗?
7.1 官方公布的数据
Bun 官方在 PR #30412 中声称:
- 二进制文件体积缩小 3-8 MB
- 性能测试在各个平台上均达到或超越原有水平
- 修复了多个长期存在的内存泄漏和 flaky 测试问题
7.2 需要独立验证的指标
作为一个被 AI 在 6 天内重写的运行时,以下指标需要独立的、可复现的基准测试:
启动时间:
# Zig 版本
hyperfine --warmup 3 'bun-zig --version'
# Rust 版本
hyperfine --warmup 3 'bun-rust --version'
Bun 最大的卖点之一是 3ms 的启动时间。如果 Rust 版本在这个指标上有任何退步,对 Claude Code 等 CLI 工具的用户体验影响将是致命的。
内存占用:
# 长时间运行的内存测试
/usr/bin/time -v bun-zig run long-running-script.js
/usr/bin/time -v bun-rust run long-running-script.js
这是 Rust 重写最核心的改进目标——如果长时间运行后内存仍然暴涨,那重写的意义就大打折扣。
包安装速度:
hyperfine --warmup 1 'bun-zig install' # cold cache
hyperfine --warmup 1 'bun-rust install' # cold cache
7.3 可能的性能影响分析
从技术角度分析 Rust 重写可能带来的性能影响:
正面因素:
- Rust 编译器的优化后端(LLVM)比 Zig 的自研后端更成熟
- Rust 的 monomorphization 可以产生更特化的代码
- Rust 的类型系统允许编译器做更激进的优化
负面因素:
- 13000 个 unsafe 块中,相当一部分是为了绕过 borrow checker,可能引入 Rust 编译器无法检查的 UB
- "忠实翻译"策略意味着代码结构可能不是最优的 Rust 写法
- 缺少 async runtime 可能导致事件循环的实现不如 Zig 版本自然
八、AI 驱动代码迁移的方法论
8.1 Bun 迁移给我们的启示
Bun 的 PORTING.md 提供了一个可复用的 AI 大规模代码迁移方法论:
Phase A: 语义投影
- 逐文件翻译,不追求 idiom
- 允许编译失败,优先保证逻辑完整
- 严格禁止创造性发挥——AI 不能"优化"或"重构"原始逻辑
- 不确定的逻辑留 TODO,不猜测
Phase B: 逐模块修复
- 逐个 crate 解决编译问题
- 逐步引入目标语言的惯用写法
- 通过测试验证行为一致性
关键原则:
- 速度来自 pipeline 自动化,不是牺牲质量
- 约束 AI 的自由度比提高 AI 的能力更重要
- 576 行的迁移指南说明:人类的核心价值在于定义规则和边界,AI 的价值在于执行
8.2 可复用的迁移脚本框架
基于 Bun 的经验,我们可以设计一个通用的 AI 驱动代码迁移框架:
# migration_framework.py
import subprocess
import json
from pathlib import Path
class MigrationFramework:
def __init__(self, source_dir: str, target_dir: str, rules: dict):
self.source_dir = Path(source_dir)
self.target_dir = Path(target_dir)
self.rules = rules
self.results = {"translated": 0, "errors": 0, "todos": 0}
def phase_a_translate(self):
"""Phase A: 逐文件忠实翻译"""
source_files = sorted(self.source_dir.rglob("*.zig"))
for src_file in source_files:
relative = src_file.relative_to(self.source_dir)
target_file = self.target_dir / relative.with_suffix(".rs")
prompt = self._build_translation_prompt(src_file)
result = self._call_ai(prompt)
# 验证翻译结果
self._validate_translation(src_file, result, target_file)
# 统计 TODO 数量
todo_count = result.count("TODO")
self.results["todos"] += todo_count
target_file.parent.mkdir(parents=True, exist_ok=True)
target_file.write_text(result)
self.results["translated"] += 1
def phase_b_fix_compilation(self):
"""Phase B: 逐 crate 修复编译"""
# 尝试编译每个 crate
crates = list(self.target_dir.glob("**/Cargo.toml"))
for crate_dir in crates:
result = subprocess.run(
["cargo", "check"],
cwd=crate_dir.parent,
capture_output=True,
text=True
)
if result.returncode != 0:
errors = self._parse_rust_errors(result.stderr)
for error in errors:
self._fix_compilation_error(crate_dir.parent, error)
def _build_translation_prompt(self, source_file: Path) -> str:
return f"""Translate this Zig file to Rust following these rules:
1. Preserve all logic exactly, do not optimize or refactor
2. Use unsafe where necessary with SAFETY comments
3. Leave TODO for uncertain logic, never guess
4. File mapping: src/xxx.zig -> crates/xxx/src/xxx.rs
5. Do NOT use tokio, rayon, hyper, futures, or async fn
6. Every unsafe block must have a SAFETY comment
Source file: {source_file.name}
Content:
{source_file.read_text()}"""
def _validate_translation(self, src, result, target):
"""基本验证:检查是否有遗漏的函数"""
# 提取源文件中的函数名
source_fns = set(re.findall(r'fn\s+(\w+)', src.read_text()))
# 提取翻译结果中的函数名
target_fns = set(re.findall(r'fn\s+(\w+)', result))
# 检查遗漏
missing = source_fns - target_fns
if missing:
print(f"WARNING: Missing functions in {target.name}: {missing}")
# ... 其他方法省略
8.3 迁移后的质量保证流程
AI 生成的代码需要比人类代码更严格的质量保证:
- 双重审查:AI 生成的代码至少需要两名人类工程师审查
- 差分测试:对相同输入,Rust 版本和 Zig 版本的输出必须完全一致
- 模糊测试:对 FFI 边界做密集模糊测试,发现 unsafe 可能引入的问题
- 内存安全测试:使用 Miri(Rust 的未定义行为检测工具)检查 unsafe 代码
- 渐进式发布:先 canary,再 stable,每个阶段都做充分验证
# 使用 Miri 检查 unsafe 代码
cargo miri test
# 使用 AddressSanitizer 检测内存错误
RUSTFLAGS="-Z sanitizer=address" cargo test --target x86_64-unknown-linux-gnu
# 使用差分测试对比 Zig 和 Rust 版本的输出
diff <(bun-zig run test.js) <(bun-rust run test.js)
九、AI 重写软件的更广泛趋势
9.1 不是孤例
Bun 的六天重写虽然最引人注目,但类似的 AI 驱动极限重写正在多个领域同时发生:
- Cloudflare 在一周内借助 AI 重新实现了 Next.js API 的大部分能力
- Ladybird 浏览器 在两周内将自己的 JavaScript 引擎从 C++ 迁移到了 Rust
- Vercel ZeroNative 在 Bun 迁移 Rust 的同时,反其道而行用 Zig 构建原生应用框架
9.2 速度与信任的博弈
AI 大规模改写基础设施时最容易被忽略的一点:AI 可以快速制造"行为相似"的代码,但基础设施项目需要的不只是行为相似。它还需要:
- 可解释的设计:为什么做了这个架构选择?AI 生成的代码很难回答这个问题
- 可追踪的历史:git blame 要能找到责任人和原因,而不是"AI 生成的"
- 可审查的变更:每个变更的 diff 必须是人类可理解的
- 出事故时的可定位性:人类维护者必须能快速定位问题所在
9.3 "AI 写、AI 审、AI 合"的危险
当整个流程变成 AI 写代码、AI 审查代码、AI 合并代码时,就出现了一个问题:谁来为代码质量负责?
在传统软件工程中,代码审查是知识传递和责任确认的关键环节。审查者不只是检查代码有没有 bug,更是在确认"我理解这段代码在做什么,我愿意为它的正确性背书"。当审查者也是 AI 时,这个背书就变得毫无意义——因为 AI 不为任何后果负责。
十、实践建议:如果你的项目也在考虑 AI 重写
10.1 什么情况下适合 AI 重写
- 代码库有完善的测试套件(覆盖率 > 90%)
- 目标语言和源语言的语义映射相对直接
- 项目有足够的资源做长期维护
- 重写后可以并行运行两个版本进行对比
10.2 什么情况下不适合
- 核心基础设施组件,下游依赖众多
- 测试覆盖率不足,无法验证行为一致性
- 涉及复杂的内存安全或并发逻辑
- 团队没有目标语言的深度经验
10.3 如果你的项目正在用 Bun
不要恐慌式删除。Bun 1.3.14 仍然是原 Zig 实现,官方也说 Rust port 还没进正式 release。更理性的做法是按用途分层看待:
包管理器用途(风险低):
- 关注 lockfile 一致性
- 确保 CI 可复现
- 做好供应链扫描
测试运行器用途(风险中):
- 确认测试语义与 Node/Vitest/Jest 的差异
- 对关键路径做交叉验证
生产运行时用途(风险高):
- 谨慎使用 Bun-only API
- 尤其注意 FFI、HTTP server、数据库、子进程、文件系统等靠近系统边界的能力
- 准备好 Node/Deno 降级方案
外部用户安装用途(风险最高):
- 不要把 Bun 作为唯一可用 runtime
- 至少提供 Node/Deno 路径
十一、总结与展望
Bun 的 Rust 重写是一次极具争议的技术实验,它同时展示了 AI 驱动代码迁移的巨大潜力和严重风险:
潜力:
- 6 天完成 96 万行代码的跨语言迁移,这是人类不可能企及的速度
- 576 行的 PORTING.md 证明:好的规则比好的 AI 更重要
- 99.8% 的测试通过率说明 AI 可以做到行为级别的兼容
风险:
- 13000 个 unsafe 块中,多少有正确的 SAFETY 注释?
- "AI 写、AI 审、AI 合"的模式让责任归属变得模糊
- 项目出走(yt-dlp、Electrobun)说明信任损失是真实的经济成本
关键教训:
- AI 是加速器,不是替代品——它可以让你更快地到达目的地,但不能替你判断方向对不对
- 速度上天,信任落地——代码可以 6 天写完,但信任需要数月甚至数年建立
- 规则比能力重要——576 行的迁移指南才是这次重写最有价值的资产
- 基础设施需要的不只是正确性——还需要可解释性、可审查性、可维护性
未来,当你的 CTO 说"我们要把代码库从 X 语言重写成 Y 语言"时,他不会再问"需要几个月",而是会问"Claude 需要几天"。但请记住:AI 可以加速迁移,不能替代信任建设。对底层工具来说,最贵的是让别人相信这段代码值得运行在自己的机器上。
选题来源: Bun Rust重写审计 unsafe
参考资料:
- Bun PR #30412: Rewrite Bun in Rust
- Bun unsafe 审计页面: Bun's unreleased Rust port has 13,365 unsafe blocks
- Claude Code Issue #33453: Memory leak in WebKit Malloc
- yt-dlp Issue #16766: Bun support limited and deprecated
- Electrobun 2.0 decoupling announcement
- Heise: AI Porting - Claude Rewrites Bun Codebase in Rust
- The Register: Bun posts Rust porting guide, says rewrite is still half-baked