5月28日 00:16

Bun 为什么选择 Zig 作为底层语言?

Bun 选择 Zig 作为底层语言,核心原因有三:

1. 与 C 的零开销互操作。 Bun 底层依赖 WebKit 的 JavaScriptCore(JSC)引擎,这是一个纯 C/C++ 库。Zig 可以直接 @cImport C 头文件,编译器自动解析 C 类型并生成 Zig 绑定,无需手写 FFI 胶水代码或使用 bindgen 工具。调用 C 函数就像调用原生函数一样。对比 Rust 调用 C 需要写 unsafe 块、手动管理 FFI 边界、处理类型映射,Zig 的方式减少了跨语言调用的性能损耗和维护成本。对于 Bun 这种深度依赖 C/C++ 库的项目,这一优势是决定性的。

zig
const c = @cImport({ @cInclude("stdio.h"); }); pub fn main() void { _ = c.printf("Hello from C\n"); }

2. 无隐藏控制流,性能完全可预测。 Zig 语言设计上没有任何隐藏行为——没有隐式内存分配、没有异常抛出、没有运算符重载背后的秘密调用、没有默认初始化。代码做什么就是什么,性能行为 100% 可预测。这对构建 JavaScript 运行时至关重要:运行时本身不能有不可控的延迟或隐式分配,否则会直接影响上层 JS 代码的执行稳定性和内存占用。

3. Comptime 编译时计算。 Zig 的 comptime 特性允许将代码标记为编译期执行,能力远超 C++ 的 constexpr——不仅可以计算常量表达式,还能在编译期执行类型操作、控制流和函数调用,动态生成类型和函数。Bun 利用 comptime 在编译期完成类型检查和代码生成优化,将运行时开销前置到构建阶段,从而在启动速度上获得显著收益。实测 Bun 在 Linux 上启动速度比 Node.js 快 4 倍,comptime 功不可没。

此外,Zig 没有垃圾回收器。Bun 的 JSC 已自带 GC,再叠加一层 GC 会造成双重内存管理的浪费和不可控的 GC 暂停。Zig 的手动内存管理配合 defer 关键字确保资源释放,让 Bun 团队能精确控制内存分配,实现近乎零开销的 HTTP 请求处理和文件操作。

值得注意的是,2026 年 5 月 Bun 团队已宣布从 Zig 迁移至 Rust(96 万行代码重写,测试兼容性达 99.8%),主要原因是项目规模化后 Zig 生态的局限性(库不丰富、社区规模有限、招聘困难)和长期内存泄漏问题。但当初选择 Zig 的技术逻辑依然成立:小团队快速原型阶段,Zig 的低摩擦和 C 互操作是加速器。

追问

Zig 的内存管理与 Rust 的借用检查器有什么本质区别?

Zig 采用手动内存管理,开发者自行决定分配和释放时机,编译器不做所有权检查,靠 defer 和 Allocator 接口规范资源管理。Rust 的借用检查器在编译期强制执行所有权规则,代码能编译就意味着数据竞争和悬垂指针不会发生。Zig 更灵活但更依赖开发者自律,Rust 更安全但有学习曲线。Bun 迁移到 Rust 的核心原因就是用编译时保证替代人工自律来消除内存泄漏。

Bun 的性能优势主要来自 Zig 还是架构设计?

主要来自架构设计。Bun 使用 JSC 而非 V8、内置 HTTP 服务器省去 libuv 中间层、集成本地打包器消除工具链切换开销,这些架构决策才是性能差异的根本。Zig 在系统级操作(文件 I/O、网络)上提供了高效实现,但 CPU 密集型任务的性能主要取决于 JSC 引擎。这也是为什么迁移到 Rust 后,Bun 的性能不会有数量级变化——架构没变,语言换了,性能特征基本保持。

如果现在要做类似的 JS 运行时,还应该选 Zig 吗?

视情况而定。如果团队小、需要快速原型验证、与 C 库深度交互,Zig 仍是好选择。如果追求长期维护性和生态成熟度,Rust 更稳妥。Bun 的迁移历程说明:原型阶段 Zig 的低摩擦和 C 互操作是加速器,规模化后 Rust 的类型安全保证和生态优势是更可持续的选择。这是一个典型的"用 Zig 验证,用 Rust 工程化"的技术演进路径。

标签:Bun