Bun (the js runtime) is being vibe-ported from zig to rust
Bun (the js runtime) is being vibe-ported from zig to rust
Zig → Rust porting guide
Zig → Rust 移植指南
You are translating one Zig file to Rust. Read this whole document before writing any code. The goal of Phase A is a draft .rs next to the .zig that captures the logic faithfully — it does not need to compile. Phase B makes it compile crate-by-crate. 你正在将一个 Zig 文件翻译为 Rust。在编写任何代码之前,请通读本文档。A 阶段的目标是在 .zig 文件旁生成一份 .rs 草稿,忠实地捕捉其逻辑——它不需要通过编译。B 阶段则负责按 crate(包)逐个使其通过编译。
Ground rules
基本原则
Write the .rs in the same directory as the .zig, same basename. <area> is always the first path component under src/ (the crate root). If the .zig basename equals its immediate parent directory name (any depth), name it mod.rs; if it equals the top-level <area> dir, name it lib.rs.
在 .zig 所在的目录下编写 .rs 文件,并保持基本文件名一致。<area> 始终是 src/ 下的第一个路径组件(即 crate 根目录)。如果 .zig 的基本文件名与其直接父目录名相同(无论深度),则命名为 mod.rs;如果它等于顶层的 <area> 目录,则命名为 lib.rs。
Examples: src/bake/DevServer/HmrSocket.zig → src/bake/DevServer/HmrSocket.rs; src/bake/DevServer/DevServer.zig → src/bake/DevServer/mod.rs; src/http/http.zig → src/http/lib.rs.
示例:src/bake/DevServer/HmrSocket.zig → src/bake/DevServer/HmrSocket.rs;src/bake/DevServer/DevServer.zig → src/bake/DevServer/mod.rs;src/http/http.zig → src/http/lib.rs。
Do not invent crate layouts. Cross-area types are referenced as bun_<area>::Type (see crate map below).
不要自创 crate 布局。跨区域类型引用方式为 bun_<area>::Type(见下方的 crate 映射表)。
Phase B wires the Cargo.toml. No tokio, rayon, hyper, async-trait, futures. No std::fs, std::net, std::process. Bun owns its event loop and syscalls. (Rust core/std slice, iter, mem, fmt, and core::ffi are fine — only the I/O-touching modules are banned.)
B 阶段负责配置 Cargo.toml。禁止使用 tokio、rayon、hyper、async-trait、futures。禁止使用 std::fs、std::net、std::process。Bun 拥有自己的事件循环和系统调用。(Rust 的 core/std 中的 slice、iter、mem、fmt 以及 core::ffi 是允许使用的——仅禁止涉及 I/O 的模块。)
No async fn. Everything is callbacks + state machines, same as the Zig.
禁止使用 async fn。一切皆为回调 + 状态机,与 Zig 中保持一致。
unsafe is fine when the Zig was already unsafe. Annotate every block with // SAFETY: <why> mirroring the Zig invariant. Leave // TODO(port): <reason> for anything you can’t translate confidently. Don’t guess. Flagging is better than wrong code.
如果 Zig 原本就是 unsafe 的,那么在 Rust 中使用 unsafe 是可以的。请为每个代码块添加 // SAFETY: <why> 注释,以反映 Zig 的不变性(invariant)。对于无法自信翻译的内容,请留下 // TODO(port): <reason>。不要猜测。标记出来比写出错误代码更好。
Leave // PERF(port): <zig idiom> — profile in Phase B wherever the Zig used a perf-specific idiom (appendAssumeCapacity, arena bulk-free, stack-fallback alloc, comptime monomorphization) and the port uses the plain idiomatic form. Phase A optimizes for correctness+idiom; Phase B greps PERF(port) and benchmarks.
如果 Zig 使用了特定于性能的惯用法(如 appendAssumeCapacity、arena 批量释放、栈回退分配、comptime 单态化),而移植版本使用了普通的惯用形式,请留下 // PERF(port): <zig idiom> 注释——以便在 B 阶段进行性能分析。A 阶段优化正确性和惯用性;B 阶段通过搜索 PERF(port) 进行基准测试。
Match the Zig’s structure. Same fn names (snake_case), same field order, same control flow. Phase B reviewers diff .zig ↔ .rs side-by-side.
匹配 Zig 的结构。保持相同的函数名(snake_case)、相同的字段顺序和相同的控制流。B 阶段的审查者会并排对比 .zig 和 .rs 文件。
Acronyms collapse to one lowercase word: toAPI→to_api, isCSS→is_css, toUTF8→to_utf8, toJS→to_js, errorInCI→error_in_ci. Rule: a run of ≥2 uppercase letters is one segment.
缩写词合并为一个小写单词:toAPI→to_api,isCSS→is_css,toUTF8→to_utf8,toJS→to_js,errorInCI→error_in_ci。规则:连续 2 个或更多大写字母视为一个片段。
Exception — out-param constructors. fn foo(this: *@This(), ...) !void whose body assigns this.* = .{...} → fn foo(...) -> Result<Self, E>. Zig uses out-params because it lacks guaranteed NRVO for error unions; Rust does not. Diff readers should expect this reshape.
例外情况——输出参数构造函数。fn foo(this: *@This(), ...) !void 且函数体执行 this.* = .{...} 的情况,应转换为 fn foo(...) -> Result<Self, E>。Zig 使用输出参数是因为它缺乏针对错误联合(error unions)的保证 NRVO(命名返回值优化);而 Rust 不需要。差异对比的阅读者应预料到这种重构。
If this is a pre-allocated slot in a pool/array (in-place init to avoid a move), keep &mut MaybeUninit<Self> and flag // TODO(port): in-place init.
如果这是一个池/数组中的预分配槽位(为了避免移动而进行的原地初始化),请保留 &mut MaybeUninit<Self> 并标记 // TODO(port): in-place init。
Exception — deinit. pub fn deinit becomes impl Drop, not an inherent method named deinit (see Idiom map).
例外情况——deinit。pub fn deinit 应转换为 impl Drop,而不是命名为 deinit 的固有方法(见惯用法映射表)。
Borrow-checker reshaping is allowed. When matching Zig flow yields overlapping &mut, capture the needed scalar (.len(), index) into a local, drop the borrow, then re-borrow. Do NOT reach for raw pointers just to silence borrowck; leave // PORT NOTE: reshaped for borrowck so Phase B diff readers aren’t confused.
允许进行借用检查器(Borrow-checker)相关的重构。当匹配 Zig 流程导致重叠的 &mut 时,将所需的标量(.len(),索引)捕获到局部变量中,丢弃借用,然后重新借用。不要仅仅为了消除借用检查错误而使用原始指针;请留下 // PORT NOTE: reshaped for borrowck 注释,以免 B 阶段的审查者感到困惑。
Prereq for every crate: #[global_allocator] static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc; must be set at the binary root before any Box/Rc/Arc/Vec mapping in this guide is valid — otherwise you silently switch from mimalloc to glibc malloc. Phase B asserts this; Phase A can assume it.
每个 crate 的先决条件:必须在二进制根目录设置 #[global_allocator] static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc;,否则本指南中任何关于 Box/Rc/Arc/Vec 的映射都将无效——否则你将静默地从 mimalloc 切换到 glibc malloc。B 阶段会对此进行断言;A 阶段可以假设已设置。