四个月的 Zig
摘要
作者回顾四个月重返 Zig 的经历,包括开发 snurl、参加 Zig Day Seattle(Andrew Kelley 出席)以及对 anyzig、内建 fuzzing、TLS 等的喜爱与不满,并与 Rust 做了对比。
暂无内容
查看缓存全文
缓存时间: 2026/10/28 01:44
# 四个月的 Zig 源地址:https://gracefulliberty.com/articles/four-months-of-zig/ 2026-10-27· 15 分钟 ·#computing (https://gracefulliberty.com/articles/tags/computing)
离开几年后,我重新回归 Zig(https://gracefulliberty.com/articles/return-to-zig)大约已经有四个月了。在这段时间里,我用 Zig 写了一些软件,并重新融入了线上和线下的社区。我决定分享一些这方面的经历,包括我对 Zig 的喜与忧。期间我也会做一些与 Rust 的对比,因为那是我之前常用的首选语言。
## 项目
我在 Zig 中启动的第一个项目是 snurl(https://codeberg.org/liberty/snurl)。这是一个用于与小型网络(Gemini、Finger、Gopher、Spartan 等)交互的库。我的目标是为小型网络提供一个 curl 的替代品,具备类似的测试和模糊测试要求,但规模更小。我希望能大致遵循 TigerStyle(https://tigerstyle.dev/)的原则,只要对项目来说合理。
我早期遇到的一个问题是模糊测试器实际上并不能正常工作。在上一篇文章中我兴奋地谈到模糊测试时,并没有意识到这个功能尚未完成。考虑到 Zig 的现状,这并不令人意外,但确实令人失望,尤其是模糊测试器还在 `zig init` 生成的 `main.zig` 文件中有所演示。
此后,我写了两个程序,但尚未发布。第一个是一个小型 TUI 打字比赛程序。到目前为止,让最小可行版本运行起来比我预期的要简单。第二个是一个使用 raylib 实现的带恒星演化的 n 体模拟器。
## Zig Day
过去几个月中最令人兴奋的机会是参加本地的 Zig Day。我在 Lobsters(https://lobste.rs/)上得知 Zig Day Seattle 将于 8 月 20 日举办。得知本地有 Zig Day 让我欣喜若狂。当我看到 Andrew Kelley 会出席时,更是兴奋不已。Zig Day 是本地聚会,对 Zig 感兴趣的人聚在一起,共同开发各自的项目,畅聊 Zig。这次的 Zig Day 上,我决定继续开发 snurl。我的朋友 Evin Yulo(https://yujiri.xyz/)也决定参加。
活动本身在 Bothell 图书馆举行,此前在 Bellevue 图书馆,因为报名人数太多导致场地容量超载才搬过来的。Zig 的创始人出席活动自然会带来这样的效果。
我喜欢 Zig Day 的这种形式,很高兴它能成为社区的一部分。由于由本地团体组织,这赋予了整个 Zig 社区一种更强的互联感。这是一个在现实中与其他对 Zig 感兴趣的人交流的方式,而且这次还有 Zig 的创始人在场。
我有机会与其他程序员交流他们的项目。希望产生了一些思想上的交叉启发,让我们的项目都变得更加强大。最终我与 evin 一起开发 snurl,他开始着手将 Zig 标准库的 TLS 实现集成为另一个后端。我还与 Andrew Kelley 简短地聊了几句。我希望 Zig 内置的模糊测试能尽快修好!
如果你想了解更多细节,Ryan Hendrickson 写了一篇精彩的文章(https://www.ryanjh.com/articles/my-first-zig-day/),从他的视角描述了那天的更多活动。我将来一定会再次参加 Zig Day Seattle。我甚至可能会去波特兰,在下一次本地活动之前先赶上一场。我很享受与其他对 Zig 及其价值观感兴趣的人建立联系的机会。
## 我喜欢的部分
### anyzig
我非常喜欢 anyzig(https://github.com/marler8997/anyzig)。我需要在不同项目中使用 zig master 和 0.16.0,anyzig 让这变得可行。遗憾的是,它在没有 `build.zig.zon` 的目录之外会失效,这意味着如果你的工具需要 `zig` 命令才能工作,那就只能在项目目录中使用。这让我那些一次性 Zig 脚本的自动格式化功能失效了。也许在这种情况下可以提供一个默认的 Zig 版本。
### IO 和分配器
我知道这让一些人感到困扰,但我很喜欢在 Zig 中必须向函数传递 IO 和分配器参数这一点。
#### 分配器
我先从分配器说起,因为它们存在的时间更长。对于每个 Zig 函数,你都必须接受一个显式的分配器参数。这与大多数编程语言不同,那些语言要么使用可以全局更改的默认分配器,要么使用由局部 `context` 更改的分配器(参见 Odin)。我喜欢能够为代码的不同部分更换分配器。你可能希望对某些东西使用 arena 分配器,而对其他东西使用通用分配器。有时一个 `FixedBufferAllocator` 就足以完成某项操作。Zig 的风格让这一切都清晰明了。
相比 Odin 的隐式 `context` 系统,我更偏好 Zig 的方式,因为 Zig 保持了显式性。一个函数是否分配内存一目了然,也很难不小心传入错误的分配器。这也优于 Rust 中隐藏的、看似不会失败的内存分配——它们在内存耗尽时会 panic。Rust 正在努力添加显式分配,但这将是又一个后来附加到语言上的特性,而非从零开始设计的功能。在 Zig 中推理涉及内存分配的程序要容易得多。
#### IO
IO 参数更加强大,尽管它更新。为了引入它而付出的破坏性变更代价完全是值得的(即使我跳过了 0.15.0 到 0.16.0 的过渡版本)。在 Zig 中,每个与底层操作系统交互的函数(有例外)都需要一个 "IO" 参数。它为输入、输出以及一切其他操作提供上下文上下文。将 Zig 移植到新的操作系统主要就是移植 IO。
实际上,这对测试非常好用。测试行为时,你只需要放入一个测试 IO 实例,让它按照你的需要工作。你可以随意改变输入流、让写入操作失败,以及做任何其他事情。就像分配一样,传递 IO 参数使得哪些程序是纯函数、哪些不是变得一目了然。你一眼就能看出一个程序是否会修改外部状态。
Zig 在保持简单的同时添加强大功能方面有着非常好的记录。它不会添加 100 个略有不同的小功能,而是倾向于提供一个主要功能来支持所有各种用例。低门槛,高上限。
### Comptime
我喜欢 comptime。它是将元编程和预计算无缝结合的方式。其他人已经写了更详细的介绍,我就不再赘述了。
### 构建系统
Zig 的构建系统(https://ziglang.org/learn/build-system/)非常出色。出色到像 Uber 这样的公司都将其用于用其他语言编写的软件。我喜欢它的一点是我可以无缝地将其他工具与它集成,而 Cargo 在这方面却会抱怨。这对于我的一个涉及代码生成的项目来说尤其重要。
### 依赖文化
Zig 拥有优秀的依赖文化。其核心理念很简单:避免依赖。当你不需要依赖时,就不要添加。然而,当你确实需要时,那通常是一个久经考验的库,Zig 可以通过 translate-c 与之轻松集成。你也可以轻松地将外部依赖引入项目内部并审计其代码。我的大多数 Zig 程序大约只有一到两个直接的 Zig 依赖,间接依赖为零。这使得代码库更容易审计和控制。这与 Rust 形成了鲜明对比——我最小的 Rust 项目也经常有几十个间接依赖。这既得益于 Zig 的依赖文化,也得益于反射、随机数生成、优雅的错误处理和 JSON 解析等基本功能是内置于语言或标准库中,而非作为独立库来维护的。不需要在每个程序中都依赖 serde 或 syn。Zig 鼓励在添加依赖之前仔细考虑,我很欣赏这一点。
### 轻松处理错误
Zig 默认就推动程序员正确处理错误。在 Zig 中处理错误最懒惰的方式是使用 `try` 关键字并在函数返回类型上加上 `!`。相比之下,Rust 会引导程序员在代码中插入 panic。问题在于,原型阶段用 panic 更方便,然后它在代码中保留的时间就会超出应有的长度。我用过 Rust 编写的生产代码,其中的 panic 替代了正确的错误返回。在 Rust 中你可以使用 `?`,比 `unwrap` 更快捷,但你还得写类似 `Result` 这样的东西。真啰嗦!或者,你可以使用 anyhow crate 返回 `anyhow::Result`,它提供了一些不错的辅助方法。但这样你又得为合理的错误处理添加一个依赖。还有 Zig 风格的可组合错误(https://mcmah309.github.io/posts/the-missing-piece-in-rust-error-handling/),又需要另一个库。
### 增量编译
Zig 的核心团队成员在增量编译方面做了出色的工作。如果你运行 `zig build --watch -fincremental`,编译器会监视文件变化,准确检查你修改了什么,然后只修改你改动的部分直至链接。不做任何超出必要的工作,因此即使对于大型代码库,速度也快到超出人类感知。Zig 从设计之初就支持这种行为。这造就了一个非常好的工作流。我可以就在文本编辑器旁边以增量模式运行编译器,每次保存代码时就能获得更新。没有时间分心去做别的事情。在 Rust 项目中,即使是较小的项目,10 秒以上的增量编译循环也很常见。这段时间足以让人分心,但又不足以高效地做其他事情,留下令人恼火的间隙——而 Zig 消除了这种间隙。Rust 在这方面处于劣势。它是一门需要维护向后兼容性的稳定语言。这意味着它不能为了提高编译速度而破坏任何东西,至少不能大幅破坏。而 Zig 不同,只要能让 Zig 变得更好,维护者愿意破坏几乎任何东西来确保最终结果完美。
目前,Zig 仅在 Linux 和 macOS 上的 x86_64 架构支持增量编译。如果你使用其他操作系统或架构,还无法使用此功能,但那又是另一个值得期待的亮点了!这让 Zig 用起来更加美妙。我欣然接受。
## 我希望改进的部分
### 更好的稳定性
和每一个 Zig 用户一样,我希望标准库能变得更稳定。我知道这是 1.0 之前最后要做的任务之一,而且在此之前还有很多其他工作,但生态系统中不断的破坏确实令人沮丧。我的理解是这就是 0.17.0 发布耗时这么长的原因。Andrew Kelley 在 Zig Day 上说他比任何人都更希望看到 1.0 的到来。我相信他,所以我会保持耐心并相信最终版本会非常出色。这也是我仍然在某些项目中使用 Rust 的唯一原因。有些场景需要一门稳定的编程语言及其提供的生态系统。但考虑到如今 Zig 中构建出的令人印象深刻的软件,稳定性并非一切。对于一些项目来说,Zig 的优点超过了它的不稳定性。我迫不及待地想看到 1.0。
### 生命周期支持
我希望 Zig 编译器能在生命周期方面提供更多帮助。具体来说,我希望返回局部变量在调试模式下能失败,可以作为一种内置的 lint(正如 Andrew Kelley 提到过想要实现的),或者在函数返回后真正使该内存失效。这样新用户就能快速学会避免这种模式。目前,即使你认为自己理解了生命周期,也很容易无意中以这种方式破坏代码。相关的,编译器目前接受那些在将来会出问题的代码。编译器允许变量在整个作用域中存活,而不是在块的作用域中存活。这意味着如果你在一个块中栈分配一个缓冲区并将其返回,该缓冲区本应失效却没有失效。我的理解是这在未来的 Zig 版本中可能会悄无声息地出问题。
```
// 这样能编译通过,但将来会失效。
const foo = blk: {
var buf: [1024]u8 = undefined;
const n = try writeToBuf(&buf);
break :blk buf[0..n];
};
```
### 更多文档
Zig 的文档很差。它足以让你入门,但要继续深入,你必须从稀疏的文档注释中揣摩意图,这些注释有时才附在函数上。示例很少。我至今还没能让 Zig 标准库的 TLS 在 snurl 中工作,因为我和一位朋友都无法弄清楚如何正确设置缓冲区。由于文档的缺失,大量关于构建健壮 Zig 程序的知识散布在整个网络上。如果你深度融入了 Zig 社区,读过现有文档、看过演示、读过代码,你就能成功。如果你没有那么深入,那就很难高效工作。这样做的好处是鼓励人们参与 Zig 社区(https://ziglang.org/community)(例如,我是 Matrix 聊天室的管理员)。当 LLM 致力于瓦解人类协作时,我们比以往任何时候都更需要人与人之间的协作。然而,我们完全可以既有社区协作又有好的文档。众所周知,即使文档写得很好,人们还是会提问。一个好的社区知道如何引导人们找到好的资源,而不是简单地说"去读文档"。
我还很好奇 Zig 与文学式编程(literate programming)的交集会是什么样子。我的文学式编程工具目前是用 Rust 写的,但那可能会改变。
### 编译时分配器
我非常希望能编译时解析 JSON。在我的一个项目中,有一个 JSON 文件存储在二进制文件内部。它是静态的,可以在编译时解析成结构体集合。然而,标准库的 JSON 解析器需要一个分配器。由于没有能在编译时工作的分配器,这意味着任何 JSON 解析都必须在运行时完成。我就是这样在我的项目中做的。我相信有人打算添加一个不需要分配器的 JSON 解析器(毕竟数据已经存在于 JSON 字符串中了)。这将解决眼前的问题,但还有更多一般性的情况可能需要分配器。我记得 `FixedBufferAllocator` 以前可以在编译时工作并服务于这个目的。它可以再次做到这一点。
### 更好的引导过程
Zig 的引导过程令人印象深刻。他们擦除了编译器的 C++ 版本,用一个小型 WASM 模块和 WASM 解释器取而代之(https://ziglang.org/news/goodbye-cpp/)。这个 WASM 文件是编译器的精简版本,它可以将完整的 Zig 编译器编译为 C 代码,然后由系统 C 编译器进行编译。这是一种有趣的方法,如果你想了解更多细节,应该阅读链接的文章。唯一的注意事项是它需要将编译好的二进制模块提交到源代码中。这比需要完整的上一版编译器(如 Rust)要好,但不如在整个过程的每个步骤都拥有完整源代码那么好。恶意行为者仍然可能在模块中夹带东西。更重要的是,许多发行版(如 Debian)目前无法正确打包 Zig,因为不可能以符合其指南的方式完成打包。他们最初的计划是使用编译器的 C 后端生成一个 C 版本的编译器,然后单独维护。他们将
相似文章
重返Zig
作者描述了从Zig到Rust再回到Zig的历程,探讨了编程语言中稳定性与表达力之间的权衡。
从Rust转向Zig的感受
一位经验丰富的Rust开发者分享了通过重新实现JSONPath工具来学习Zig的旅程,重点介绍了IDE支持和代码组织方面的差异。
Zig之旅
这篇博客文章回顾了参加Zigtoberfest的经历,并为使用Zig编程语言提供了分层建议,重点关注系统思维、设计深度和软件完美。
我对Bun用Rust重写的看法
Zig的创建者Andrew Kelley分享了他对Bun从Zig重写为Rust的决定的看法,讨论了项目的历史、Oven公司的管理问题以及代码质量方面的担忧。
Zig 示例教程
通过带注释的示例,对 Zig 编程语言进行实践性介绍,涵盖从基础到高级的主题。灵感来源于 Go by Example。