在 Rust 中重写 Bun
披露:Bun 于2025年12月被 Anthropic 收购。我和 Bun 团队的其他成员在 Anthropic 工作。我在 Rust 重写的大部分时间里使用了 Claude Fable 5 的预发布版本。Bun 最初是将 esbuild 的 JavaScript 和 TypeScript 转换器从 Go 移植到 Zig 的逐行重写。2021年4月16日,我写下了我的第一行 Zig 代码。在看到 Hacker News 上的单页 Zig 语言参考并对底层控制和性能关注感到非常兴奋后,我支持 Zig。从一开始,Bun 的范围就非常大:JavaScript、TypeScript 和 CSS 转换器,压缩器,打包 npm 兼容的包管理器,Jest 类似的测试运行器,Node.js 和 TypeScript 兼容的模块解析,HTTP/1.1 和 WebSocket 客户端,Node.js API 实现如 fs、net、tls 以及其他几十个模块。Bun 的初始版本是我在一年内编写的,在一个狭窄的奥克兰公寓里,在没有 LLM 的情况下,使用 Zig 构建。像 Bun 这样的雄心勃勃的项目的默认结果是加入 GitHub 个人资料页面的死去副项目的墓地。如果没有 Zig,我不可能在一年内构建这么多东西。现在,Bun 的 CLI 每月下载量超过 2200 万次。Claude Code 和 OpenCode 等流行工具将 Bun 作为它们的运行时。Vercel、Railway、DigitalOcean 等公司对 Bun 提供了一方支持。Bun 的范围对于稳定性而言也是一个挑战。这是我们在 Bun v1.3.14 中修复的一小部分错误示例:节点:zlib 中 .reset() 方法调用 zlib、Brotli 或 Zstd 流时的堆使用后释放崩溃,而异步 .write() 仍在线程池中进程中;节点:zlib 中,当 onerror 回调发出重入 write() 后跟 close() 运行本地句柄时的使用后释放崩溃;节点:http2 中的使用后释放崩溃,当重入的 JS 回调(例如 session.request() 在超时监听器、选项获取器或写入回调中)触发了 hashmap 再哈希,导致内部流指针无效;UDPSocket.send() 和 sendMany() 中的使用后释放,用户代码在 valueOf() 或 toString() 回调中可能在有效负载捕获和实际发送之间分离一个 ArrayBuffer;Buffer#copy 和 Buffer#fill 中的崩溃和越界读取,当 valueOf 回调在参数强制转换期间分离或调整基础 ArrayBuffer 的大小;在 UDPSocket.sendMany() 中的堆越界写入,当 socket 的连接状态在迭代中通过用户 JS 回调发生变化;crypto.scrypt 中的内存泄漏,当输出缓冲区分配失败时,回调和受保护的密码/盐缓冲区从未释放;SSLWrapper.init 在错误路径上泄漏了 strdup 的密码短语;tlsSocket.setSession() 中的内存泄漏,每次调用泄漏一个 SSL_SESSION(每次调用约 6.5 KB),由于 d2i_SSL_SESSION 后缺少 SSL_SESSION_free;内存泄漏,fs.watch() 观察者在 .close() 之后从未被垃圾收集,由于引用计数下溢使每个观察者永久固定为 GC 根;CSS 解析器中的双重释放崩溃,当 background-clip 有供应商前缀和多层背景时;DuplexUpgradeContext 从未被释放——每个 tls.connect({ socket: duplex }) 都是一次完整泄漏;MessageEvent 中的竞争条件崩溃,当 GC 标记线程可以在来自 BroadcastChannel 或 MessagePort 的并发访问期间观察到 m_data 中的撕裂变体时。我们本可以不停地逐个修复这些类型的错误,但我们有责任对依靠我们的用户做得更好,并系统地防止这些类型的错误再次出现。我们已经在做的事情:我们修补了 Zig 编译器,以添加地址消毒器支持。我们在每次提交时都使用 ASAN 运行我们的测试套件。我们在 Windows 上发布 Zig 安全检查的 ReleaseSafe 构建。我们使用 Fuzzilli 对 Bun 的运行时 API 进行 24/7 的模糊测试,Fuzzilli 是 V8 和 JavaScriptCore 使用的 JavaScript 引擎模糊测试工具。我们有很多端到端内存泄漏测试。这比许多项目做得还要多。只是非常聪明,不要犯错误?我们的错误修复列表让人感到不安,我也厌倦了在晚上睡觉前担心 Bun 的崩溃。我不责怪 Zig——Zig 的其他用户并没有我们遇到的错误,并且将 GC 与手动管理的内存混合对于软件来说是一个不常见的事情,以至于没有语言真的为此设计。如果没有 Zig,我们无法走到这一步,我将永远感激。直到最近,编程语言的选择对于像 Bun 这样的项目来说都是单向决策。JavaScript 是一种垃圾收集语言,现代 JavaScript 引擎如 JavaScriptCore(和 V8)对异常处理和垃圾收集器有严格的规则。Zig 与 C 一样,不为你管理内存,这是许多项目使用 Zig 的一个很好的理由。Zig 没有构造函数/析构函数,预期在每个调用网站显式编写大多数清理代码,使用 defer。对于 Bun,正确处理垃圾收集值和手动管理值的生命周期一直是主要的稳定性问题——大多数...
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡