已经有了 C++、D 和 Rust,为什么还要使用 Zig?


Zig是一种便携式语言,具有无隐藏控制流、无隐藏分配以及对无标准库的一流支持,旨在提高可读性并提供可重用性。

Zig提供了可选的标准库,每个std lib API 仅在使用时才会编译到程序中,同时支持与libc链接和不链接,适用于裸机和高性能开发。

Zig不仅是一种编程语言,还提供了一个包管理器和构建系统,可用于现有的C/C++项目,使得编译和交叉编译变得简单,同时消除系统依赖。

无隐藏的内存分配
几乎所有垃圾收集语言都散布着隐藏的内存分配,因为垃圾收集器隐藏了清理方面的证据。

隐藏分配的例子:

  • Go 的 defer 会将内存分配到函数本地堆栈。这种控制流的工作方式并不直观,如果在循环中使用 defer,还可能导致内存不足的故障。
  • C++ 的例程会分配堆内存,以便调用例程。
  • 在 Go 中,函数调用会导致堆分配,因为 goroutines 会分配小堆栈,当调用堆栈足够深时,堆栈大小会被调整。
  • Rust 标准库的主要应用程序接口会在内存不足的情况下产生panic,而接受分配器参数的备用应用程序接口则是事后才想到的(参见 rust-lang/rust#29802)。

隐藏分配的主要问题在于,它妨碍了代码的可重用性,不必要地限制了适合部署代码的环境数量。

在 Zig 中,有一些标准库功能可以提供堆分配器并与之协同工作,但这些都是可选的标准库功能,而不是内置于语言本身。如果你从不初始化堆分配器,你就可以确信你的程序不会进行堆分配。

每个需要分配堆内存的标准库功能都会接受一个 Allocator 参数来完成分配。这意味着 Zig 标准库支持独立目标。例如,std.ArrayList 和 std.AutoHashMap 可用于裸机编程!

自定义分配器让手动内存管理变得轻而易举。Zig 有一个调试分配器,它能在使用后释放(use-after-free)和双倍释放(double-free)的情况下保持内存安全。它能自动检测并打印内存泄漏的堆栈轨迹。

此外,Zig 还提供了一个arena分配器,这样你就可以将任意数量的分配捆绑到一个分配器中,然后一次性释放所有分配,而不用单独管理每个分配。特殊用途分配器可用于提高性能或内存使用率,以满足任何特定应用程序的需求。

为库设计的可移植语言
编程的圣杯之一是代码重用。遗憾的是,在实践中我们发现自己多次重复发明轮子。很多时候这是有理由的:

  • 如果一个应用程序有实时性需求,那么任何使用垃圾收集或任何其他非确定性行为的库都将不予考虑。
  • 如果一门语言让人太容易忽略错误,因此不得不验证一个库是否正确地处理和抛出错误,就很容易因此放弃这个库并重新实现它,只有自己知道自己正确地处理了所有相关的错误。Zig的设计使程序员能做的最懒的事情就是正确处理错误,因此人们可以合理地相信一个库会正确地抛出错误。
  • 目前,实事求是的说,C语言是最通用、最可移植的语言。任何不具备与C代码互操作能力的语言都有可能被历史所抛弃。Zig试图成为编写库的新的可移植语言,同时使导出函数直接符合C ABI,并引入安全性和防止实现中的常见错误的语言设计。


简单性
C++、Rust和D有大量的特性,它可能会打乱你正在编写的应用程序的实际含义。人们发现自己是在调试自己的编程语言知识,而不是调试应用程序本身。
Zig没有宏也没有元编程,但仍然足够强大,可以清晰、不重复地表达复杂的程序。即使是在有宏的 Rust 里, format! 也是特例,它是在编译器内部实现的。与此同时Zig中的等价函数是在标准库中实现的,编译器中没有特例代码。

工具性
可以从下载页面下载Zig。Zig提供了Linux、Windows、MacOS和FreeBSD的二进制存档。你将得到:

  • 通过下载并解压单个压缩包进行安装,无需配置系统
  • 静态编译,没有运行时依赖
  • 使用成熟的、得到良好支持的LLVM基础架构,支持大多数主要平台,并进行深度优化
  • 开箱即用的支持大多数主要平台上交叉编译
  • 提供libc的源代码,在任何支持的平台上需要时都会动态编译
  • 包括带缓存的构建系统
  • 编译具有libc依赖的C/C++项目