对于系统编程而言,C 语言由于其小的占用空间和与机器硬件的接近性,仍然是基础标准。然而,开发者经常面临 C 语言在手动管理资源和错误状态方面的阻碍。Zig 被定位为一种现代替代方案,它保留了这种底层哲学,但引入了旨在消除特定耗时且易错模式的特性。与引入大量繁琐操作以实现内存安全 Rust 不同,Zig 专注于使 C 语言的常见任务更稳健且更易于测试。

Zig 的主要改进之一是它处理资源清理的方式。在 C 语言中,开发者通常使用 `goto` 标签来确保如果函数在中间失败,之前分配的资源能按正确的逆序释放。Zig 用 `errdefer` 取代了这一机制,该机制将清理指令放置在分配操作旁边。Zig 仅在函数返回错误时才自动按逆序执行这些指令。这种方法不仅简化了代码,还允许更细粒度的测试;例如,开发者可以使用专门的“FailingAllocator”来验证每一个特定的失败路径是否正确触发了相应的清理操作且没有内存泄漏。

展示 Zig 中用于资源清理的 errdefer 使用的代码列表。
Zig 的 errdefer 机制允许比 C 语言的 goto 标签更整洁的资源管理。www.mtsoukalos.eu

这张图片展示了文中描述的 Zig errdefer 机制,显示了清理指令如何放置在分配操作旁边。

显示带有多个 errdefer 语句的 Zig 函数的代码列表。
Zig 如何自动按逆序处理多个清理任务的示例。www.mtsoukalos.eu

此视觉效果提供了第二段中提到的自动逆序清理逻辑的具体示例。

Zig 还解决了关于内存边界和错误处理的常见 C 语言陷阱。在 C 语言中,传递给函数的数组通常被视为原始指针,这使得意外访问越界索引变得很容易。Zig 使用“切片”(slices),将数据与其长度捆绑在一起,允许编译器在调试和安全模式下捕捉此类错误。此外,虽然 C 语言允许开发者忽略像 `close()` 这样的函数的返回值,但 Zig 将错误视为返回类型的一部分。除非开发者使用 `try`、`catch` 或 `if` 语句显式处理错误,否则编译器将拒绝构建程序。Zig 不使用基于文本操作的 C 预处理器,而是使用 `comptime` 在编译时运行普通的 Zig 代码,为通用数据结构和配置提供更好的类型检查。

显示返回错误类型的 Zig 函数的代码列表。
Zig 的显式错误处理要求开发者使用 try 或 catch 处理返回值。www.mtsoukalos.eu

这张图片支持了关于 Zig 如何将错误视为返回类型一部分的讨论,这与 C 语言的可选返回值不同。

展示 Zig 切片及其长度属性的代码列表。
Zig 切片将数据与长度捆绑在一起,以防止越界内存访问。www.mtsoukalos.eu

此截图展示了第三段中描述的“切片”概念,作为解决 C 语言原始指针陷阱的方案。

尽管有这些优势,Zig 在稳定性方面涉及特定的权衡。Zig 并未消除所有的内存错误;它仍然允许使用后释放(use-after-free)漏洞,且不会防止指针悬挂。此外,在“fast”和“small”模式下,许多安全检查会被禁用,以匹配 C 语言的性能特征。由于 Zig 仍在演进中,它缺乏 C 语言提供的数十年的工具链、文档和硬件支持。对于在仅有厂商特定 C 编译器的旧系统或微控制器上工作的开发者来说,C 语言仍然是务实的选择,但对于新项目,Zig 提供了一种在拥有更现代安全网的情况下编写接近机器代码的方法。

Hacker News 读者的反应

  • 替代方案: 一位评论者对项目的可靠性表示担忧,指出他们观察到 Zig 重写 MUSL 库时出现了反复出现的重大错误。 评论 ↗
  • 质疑: 另一位用户建议这些问题对于 1.0 版本之前的版本可能不是灾难性的,并指出用户可能链接到其他的 libc 实现。 评论 ↗
  • 质疑: 一位评论者认为该项目的小团队规模使得快速优先处理特定的错误修复或功能变得困难。 评论 ↗

以上为 Hacker News 讨论中观点的摘要,并非经过核实的事实。 阅读完整讨论 ↗