Zig

Zig是一种通用系统编程语言，采用静态类型。旨在成为C语言的继任者、提供更轻量更简单的编程体验、提供更丰富的功能，以MIT许可证发布。

Zig和C语言的区别主要在控制流、函数调用、库导入、变量声明和Unicode支持，且Zig不支持宏和预处理指令。Zig从现代语言引入了编译期泛型编程特性，允许函数在编译期处理各种数据，同时添加了一些新的编译器指令，以便通过反射访问和修改类型信息。

像C语言一样，Zig没有实现垃圾回收，而是选择手动管理内存，并添加了提高代码安全性的功能，例如可选类型和单元测试框架。

Zig最初由Andrew Kelley设计，于2016年发布，开发现由Zig软件基金会资助。

## 描述

### 目标

Zig 的主要目标是成为解决目前由 C 解决的各种任务的更好的解决方案。对此的主要关注点是可读性；Zig 尽可能使用现有概念和语法，避免为相似概念添加不同的语法。此外，它的设计目标是“稳健性、最佳化和可维护性”，包括各种提高安全性、优化和测试的功能。简洁的语法是维护的重要组成部分，因为该语言的目标是允许维护人员在不需要学习他们可能不熟悉的语言细节的情况下调试代码。即使有这些更改，Zig 也可以编译并与现有的 C 代码一起使用；可以在 Zig 项目中包含 C 头文件并调用它们的函数，并且，通过包含编译器生成的头文件，Zig 代码可以被链接到 C 项目。

为了保持整体设计理念的简洁和易读，Zig 系统整体相比于 C 及其他类似 C 的语言也包含了一些风格上的变化。例如，Rust 语言具有 运算符重载，这意味着类似 的语句实际上可能是对类型的重载版本的加运算符的函数调用。此外，该函数可能会引发 panic，从而可能中断任何后续代码。在 Zig 中，如果某个东西调用了一个函数，那么它看起来就像一个函数调用；如果没有调用，它看起来就不会像一个函数调用。如果它可能抛出错误，则一定会在代码中显式表示它可能抛出错误，而错误处理可以通过错误类型处理，也可以通过 catch 或 try 处理。

Zig 的目标与同时期设计的许多其他语言（如 Go、Rust、Carbon 和 Nim）的目标形成对比。通常，这些语言更复杂，他们添加了运算符重载、看起来像值（属性）调用的函数等许多功能，这些旨在帮助构建大型程序。并且这些特性更接近于 C++ 的特性，而这些语言也更像 C++。Zig 在类型系统的扩展上更为保守，支持编译时泛型，并通过 comptime 特性实现了一种 鸭子类型 形式的用法。

### 内存处理

C 程序中错误的主要来源之一是基于malloc的内存管理系统。malloc 会为代码使用分配一块内存，并返回该内存的引用作为一个 指针，但C没有一套系统来确保在程序不再需要内存时释放该内存，这可能导致内存泄漏以至于耗费完所有的可用内存。更常见的是悬空指针，这些指针引用未正确分配的内存地址。

解决这些问题的常见方法是垃圾回收（GC），它会检查程序中指向先前分配过内存的指针，并删除所有的不再有指针指向它们的内存块。尽管这在很大程度上减少甚至消除了内存错误，但GC系统的速度相对较慢，并且具有不可预测的性能，使其不适用于系统编程。另一种解决方案是自动引用计数（ARC），其通过维护指向一个内存块的指针数，并在指针创建和销毁时更新计数来实现基本相同的功能，这意味着其不需要执行详尽的指针搜索，而仅增加每个指针在执行创建和销毁操作时更新引用计数器的开销。

Zig 旨在提供与 C 相似或更好的性能，因此 GC 和 ARC 不是合适的解决方案。相反，它使用一种现代的（截至 2022 年），被称为可选类型的概念。与指针可以指向空值或 nil 不同，这种方案使用一个单独的类型来指示可能为空的数据。这类似于使用一个指针和一个布尔值的结构来指示指针是否有效，但布尔值的状态由语言隐式管理，不需要程序员显式管理。因此，例如，当声明指针时，它被设置为“未分配”，而当该指针从 malloc 接收一个值时，如果 malloc 成功，它将被设置为“已分配”。

这种模型的优点是它具有非常低甚至是零的开销；尽管这使得编译器必须创建在操作指针时传递可选类型，而不是一个简单的指针的代码，但这允许它在编译时直接检查出可能的内存问题，而无需延后到运行时检查。例如，在 C 中创建一个具有空值的指针并尝试使用它是完全可以接受的，尽管这会导致空指针错误。相比之下，使用可选类型的语言可以保证所有代码只会在指针有效时尝试使用相应的指针。虽然这不能消除所有潜在问题，但当在运行时发生问题时，错误可以被更精确地定位和解释。

Zig 中内存管理的另一变化是：实际分配操作是通过 struct 描述相应的操作来处理的，而不是直接调用 libc 中的内存管理函数。例如，在 C 中，如果想写一个包含多个副本的字符串的函数，该函数可能如下所示：

```c
const char* repeat(const char* original, size_t times);
```

在代码中，该函数会检查 original 的大小，然后 malloc times 的内存来为它将构建的字符串预留空间。但这个 malloc 对调用它的函数是不可见的，所以，如果像 `repeat` 这样的函数没有正常释放内存，就会发生内存泄漏。而在 Zig 中，这种操作可能通过如下函数处理：

```zig
fn repeat(allocator: *std.mem.Allocator, original: []const u8, times: usize) std.mem.Allocator.Error![]const u8;
```

在此代码中，allocator 变量传递了一个内存分配器，并且 repeat 函数则返回结果字符串或 Allocator.Error。通过直接将分配器作为输入传参，使得内存分配不会被“隐藏”起来，即调用链上的所有内存分配都会通过这个分配接口进行分配。顺带一提，Zig 的标准库内没有进行任何隐式内存分配。此外，由于结构可以指向任何东西，可以使用替代分配器，甚至是程序中编写的分配器。这使不使用通常分配整个内存页的操作系统函数的小对象分配器等操作成为了可能。

可选类型是一个提供通用功能但仍然简单和通用的语言特性。它不仅可以用于解决空指针问题，还可以明确地描述“无值”场景。考虑一个名为 countTheNumberOfUsers 的函数，它返回一个整数，以及一个整数变量，theCountedUsers，它保存结果。在许多语言中，会在 theCountedUsers 中放置一个 魔术数字 以表示 countTheNumberOfUsers 尚未被调用，而许多实现则会将其设置为零。在 Zig 中，这可以实现为 var theCountedUsers: ?i32 = null，将变量设置为明确的“未被调用”的值。

Zig 另一个有助于管理内存问题的更通用特性是 defer 的概念，由它标记的代码在函数结束时无论如何都会被执行，包括可能的运行时错误。如果某个特定函数分配了一些内存，然后在操作完成时释放它，可以添加一行代码，推迟一个 free，以确保无论发生什么都会释放它。但需要注意的是，defer 并不完全等价于 RAII，在某些场景下，会有许多区别之处。

Zig 内存管理避免了隐式分配。即分配不直接由语言本身进行管理。相反，用户需要通过 标准库 明确地进行堆访问来进行显式内存管理。

### 与 C 的直接交互

Zig 提倡一种将新 Zig 代码与现有 C 代码结合的渐进方法。为此，它可以尽可能无缝地与现有 C 库进行交互。Zig 使用 @import 指令导入库，通常如下所示：

```zig
const std = @import("std");
```

如此便可以调用 std 内的函数，例如：

```zig
std.debug.print("Hello, world!\n", .{});
```

要加载 C 代码，只需将 @import 替换为 @cImport：

```zig
const c = @cImport(@cInclude("soundio/soundio.h"));
```

然后我们就可以像调用本地 Zig 代码一样调用 soundio 库中的函数。由于 Zig 使用新数据类型，它们是显式定义的，不像 C 的更通用的 和 float，我们需要使用少量指令在 C 和 Zig 类型之间移动数据，包括 @intCast 和 @ptrCast。

### 交叉编译

Zig 将交叉编译视为语言的一级用例。这意味着任何 Zig 编译器都可以为其目标平台之一生成可运行的二进制文件，这些平台包括数十种。这些不仅包括广泛使用的现代系统，如 ARM 和 x86-64，还包括 PowerPC、SPARC、MIPS、RISC-V 甚至 IBM 的 z/Architectures (S390)。工具链可以编译到这些目标中的任何一个，而无需安装额外的软件，因为所有需要的支持都在基本系统中。

### 编译时计算

通过使用 comptime 关键字，程序员可以显式地在 编译时 而不是 运行时 评估代码段。能够在编译时运行代码使得 Zig 具有 宏 和 条件编译 的功能，而无需单独的 预处理器 语言。

在编译时，类型成为 一级公民。这使得在编译时 鸭子类型 成为可能，这也是 Zig 实现泛型类型的方式。

例如，在 Zig 中，一个泛型的 链表 类型可以使用如下函数实现：

```zig
fn LinkedList(comptime T: type) type;
```

这个函数接收某种类型 T，并返回一个自定义的 struct，定义了包含该数据类型的链表。

### 名称的由来

据报道，“Zig”这个名字是通过一个涉及 Python 脚本的过程选出的，该脚本随机组合字母，从字母“Z”开始，然后跟随一个元音或“Y”，以生成四个字母的单词。尽管目标长度是四个字母，但在生成的各种组合中，最终选择了三个字母的单词“Zig”。

## 其他特性

Zig 支持 编译时 泛型编程、反射编程 和评估、交叉编译 以及 手动内存管理。该语言的一个主要目标是改进 C 语言，同时也从 Rust 等语言中汲取灵感，等等。Zig 具有许多用于 低级编程语言 的特性，特别是紧凑结构（没有字段之间的填充的结构）、任意宽度的整数 和多种指针类型。

Zig 不仅仅是一种新语言：它还包括一个 C/C++ 编译器，可以与这两种语言一起使用。

## 缺点

Zig 有一些缺点。如果内存没有正确释放，由于缺乏隐式控制，可能会因为忘记 `defer` 等必要的手动操作导致内存泄漏。对于那些不熟悉低级编程概念的人来说，学习 Zig 的曲线可能很陡峭。尽管 Zig 社区在不断壮大，但截至 2024 年，它仍然是一个新的语言，在成熟度、生态系统和工具方面有待改进。与其他语言的互操作性可能会带来挑战，因为这通常需要额外的努力来管理数据编组和通信。最后，复杂用例的学习资源有限，尽管随着兴趣和采用的增加，这种情况正在逐渐改善。

## 版本

自 0.10 版本以来，（新的默认）Zig 编译器是用 Zig 编程语言编写的，即它是一个 自举编译器，这是该版本的一个重大新特性。旧的遗留 自举 编译器是用 C++ 编写的，仍然是一个选项，但在 0.11 版本中将不再是选项。当使用新的 Zig 编译器进行编译时，使用的内存要少得多，并且编译速度稍快。旧的、现在的遗留 C++ 编译器使用了 3.5 倍的内存。

Zig 的默认优化后端仍然是 LLVM，并且 LLVM 是用 C++ 编写的。带有 LLVM 的 Zig 编译器是 169 MiB，而不带 LLVM 的则是 4.4 MiB。通常情况下，使用新的基于 Zig 语言的编译器编译的可执行代码更快，其 LLVM 代码生成更好，并修复了许多错误，但在 0.10 版本中也对旧的遗留编译器进行了改进。自托管链接器与自托管编译器紧密耦合。新版本还增加了一些对 AMD GPU 的实验性（第 3 层）支持（源代码中还有对 Nvidia GPU 和 PlayStation 4 和 5 的一些较少支持）。

旧的 自举 ("stage1") 编译器是用 Zig 和 C++ 编写的，使用 LLVM 13 作为后端，支持其许多本机目标。编译器也是 自由及开放源代码软件，在 MIT 许可证 下发布。Zig 编译器提供了类似于 Clang 的编译 C 和 C++ 的能力，命令为 `zig cc` 和 `zig c++`，提供了包括 C 标准库 (libc) 和 C++ 标准库 (libcxx) 在内的许多不同平台的头文件，允许 Zig 的 `cc` 和 `c++` 子命令作为开箱即用的 交叉编译器。

此外，官方支持（并记录）的操作系统（主要是桌面操作系统）上可以制作（最小）应用程序，这些应用程序也可以为 Android（使用 Android NDK）和 iOS 编程。

## 包管理

在0.11.0版本之前，Zig没有内置的包管理器，但在0.11.0版本中包含了一个实验性的包管理器，并在0.12.0中进一步扩展。

Zig没有官方的包仓库；相反，包只是一个指向归档文件或Git仓库的URL。理想情况下，当解压缩时，包括一个标准的`build.zig`文件（Zig 编译器按惯例使用该文件来编译源代码）和一个`build.zig.zon`文件，用于定义包的名称和版本。

Zig 的开发由 Zig 软件基金会（ZSF）资助，ZSF 是一个由 Andrew Kelley 担任总裁的非营利公司，接受捐赠并雇佣多名全职员工。

## 示例

### 泛型链表

```zig
const std = @import("std");
const stdout = std.io.getStdOut().writer();

fn LinkedList(comptime T: type) type {
    return struct {
        const Self = @This();
        pub const Node = struct {
            next: ?*Node = null,
            data: T,
        };

        first: ?*Node = null,

        pub fn prepend(
            list: *Self,
            new_node: *Node,
        ) void {
            new_node.next = list.first;
            list.first = new_node;
        }
        pub fn format(
            list: Self,
            comptime fmt: []const u8,
            options: std.fmt.FormatOptions,
            out_stream: anytype,
        ) !void {
            try out_stream.writeAll("( ");
            var it = list.first;
            while (it) |node| : (it = node.next) {
                try std.fmt.formatType(
                    node.data,
                    fmt,
                    options,
                    out_stream,
                    1,
                );
                try out_stream.writeAll(" ");
            }
            try out_stream.writeAll(")");
        }
    };
}

pub fn main() !void {
    const ListU32 = LinkedList(u32);
    var list = ListU32{};
    var node1 = ListU32.Node{ .data = 1 };
    var node2 = ListU32.Node{ .data = 2 };
    var node3 = ListU32.Node{ .data = 3 };
    list.prepend(&node1);
    list.prepend(&node2);
    list.prepend(&node3);
    try stdout.print("{}\n", .{list});
    try stdout.print("{b}\n", .{list});
}
```

- 输出

```output
( 3 2 1 )
( 11 10 1 )
```

### 带分配器的字符串重复

```zig
const std = @import("std");

fn repeat(
    allocator: *std.mem.Allocator,
    original: []const u8,
    times: usize,
) std.mem.Allocator.Error![]const u8 {
    var buffer = try allocator.alloc(
        u8,
        original.len * times,
    );

    for (0..times) |i| {
        std.mem.copyForwards(
            u8,
            buffer[(original.len * i)..],
            original,
        );
    }

    return buffer;
}

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();

    var arena = std.heap.ArenaAllocator.init(
        std.heap.page_allocator,
    );
    defer arena.deinit();

    var allocator = arena.allocator();

    const original = "Hello ";
    const repeated = try repeat(
        &allocator,
        original,
        3,
    );

    // 输出 "Hello Hello Hello "
    try stdout.print("{s}\n", .{repeated});
}
```

- 输出

```output
Hello Hello Hello
```

## 社区

Zig 软件基金会（ZSF）有一个非常活跃的贡献者社区，并且仍处于早期发展阶段。尽管如此，2024 年的一项 Stack Overflow 调查发现，Zig 软件开发人员的平均年薪为 103,000 美元，使其成为薪资最高的编程语言之一。然而，只有 0.83% 的受访者表示他们精通 Zig。

## 项目

- Bun 是一个用 Zig 编写的 JavaScript 和 TypeScript 运行时，使用 Safari 的 JavaScriptCore 虚拟机。
- Ghostty 是一个用 Zig 编写的 终端模拟器。
- TigerBeetle 是一个用 Zig 编写的金融交易数据库。

## 基本资料

- 编程范型：多范式：命令式、并发、过程式、函数式
- 设计者：Andrew Kelley
- 发行日期：2016年2月8日
- 类型系统：静态、强、推断、名义型别、泛型
- 平台：x86-64、ARM64、WebAssembly、Tier 2：ARM、IA-32、RISC-V、MIPS64、POWERPC64、SPARC64、LoongArch
- 操作系统：跨平台：Linux、FreeBSD、Windows
- 许可证：MIT
- 文件扩展名：.zig、.zir、.zon
- 受影响于：C、C++、LLVM IR、Go、Rust