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.zigsrc/bake/DevServer/HmrSocket.rs; src/bake/DevServer/DevServer.zigsrc/bake/DevServer/mod.rs; src/http/http.zigsrc/http/lib.rs. 示例:src/bake/DevServer/HmrSocket.zigsrc/bake/DevServer/HmrSocket.rssrc/bake/DevServer/DevServer.zigsrc/bake/DevServer/mod.rssrc/http/http.zigsrc/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。禁止使用 tokiorayonhyperasync-traitfutures。禁止使用 std::fsstd::netstd::process。Bun 拥有自己的事件循环和系统调用。(Rust 的 core/std 中的 slice、itermemfmt 以及 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: toAPIto_api, isCSSis_css, toUTF8to_utf8, toJSto_js, errorInCIerror_in_ci. Rule: a run of ≥2 uppercase letters is one segment. 缩写词合并为一个小写单词:toAPIto_apiisCSSis_csstoUTF8to_utf8toJSto_jserrorInCIerror_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). 例外情况——deinitpub 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 阶段可以假设已设置。