Zig 语言参考

简介 §

Zig 是一种通用编程语言和工具链,用于维护健壮优化可复用的软件。

健壮性
即使在内存不足等边缘情况下,行为也能保持正确。
优化性
以最佳方式编写程序,使其表现和性能达到最佳。
可复用性
相同的代码可在具有不同约束的多种环境中运行。
可维护性
向编译器和其他程序员精确传达意图。该语言对代码阅读的开销很低,并且能够适应不断变化的需求和环境。

学习新事物最有效的方法通常是查看示例,因此本文档展示了如何使用 Zig 的每个特性。它都在一个页面上,因此您可以使用浏览器的搜索工具进行搜索。

本文档中的代码示例作为 Zig 主测试套件的一部分进行编译和测试。

此 HTML 文档不依赖任何外部文件,因此您可以离线使用。

Zig 标准库 §

Zig 标准库》拥有自己的文档。

Zig 标准库包含常用的算法、数据结构和定义,可帮助您构建程序或库。您将在本文档中看到许多使用 Zig 标准库的示例。要了解有关 Zig 标准库的更多信息,请访问上面的链接。

或者,Zig 标准库文档随每个 Zig 发行版提供。可以通过本地 Web 服务器渲染:

Shell
zig std

Hello World §

hello.zig
const std = @import("std");

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}
Shell
$ zig build-exe hello.zig
$ ./hello
Hello, world!

大多数情况下,将消息写入 stderr 比写入 stdout 更合适,并且消息是否成功写入流并不重要。对于这种常见情况,有一个更简单的 API:

hello_again.zig
const std = @import("std");

pub fn main() void {
    std.debug.print("Hello, world!\n", .{});
}
Shell
$ zig build-exe hello_again.zig
$ ./hello_again
Hello, world!

在这种情况下,main 函数的返回类型中的 ! 可以省略,因为该函数不返回任何错误。

另请参阅

注释 §

Zig 支持 3 种注释。普通注释会被忽略,但文档注释和顶层文档注释会被编译器用于生成包文档。

生成的文档仍处于实验阶段,可以通过以下方式生成:

Shell
zig test -femit-docs main.zig
comments.zig
const print = @import("std").debug.print;

pub fn main() void {
    // Comments in Zig start with "//" and end at the next LF byte (end of line).
    // The line below is a comment and won't be executed.

    //print("Hello?", .{});

    print("Hello, world!\n", .{}); // another comment
}
Shell
$ zig build-exe comments.zig
$ ./comments
Hello, world!

Zig 中没有多行注释(例如,像 C 语言中的 /* */ 注释)。这使得 Zig 具有以下特性:每行代码都可以在不依赖上下文的情况下进行标记化。

文档注释 §

文档注释是开头恰好有三个斜杠(即 /// 而非 ////)的注释;连续的多个文档注释会合并成一个多行文档注释。文档注释记录紧随其后的内容。

doc_comments.zig
/// A structure for storing a timestamp, with nanosecond precision (this is a
/// multiline doc comment).
const Timestamp = struct {
    /// The number of seconds since the epoch (this is also a doc comment).
    seconds: i64, // signed so we can represent pre-1970 (not a doc comment)
    /// The number of nanoseconds past the second (doc comment again).
    nanos: u32,

    /// Returns a `Timestamp` struct representing the Unix epoch; that is, the
    /// moment of 1970 Jan 1 00:00:00 UTC (this is a doc comment too).
    pub fn unixEpoch() Timestamp {
        return Timestamp{
            .seconds = 0,
            .nanos = 0,
        };
    }
};

文档注释只允许出现在特定位置;在意外位置(例如表达式中间或非文档注释之前)出现文档注释会引发编译错误。

invalid_doc-comment.zig
/// doc-comment
//! top-level doc-comment
const std = @import("std");
Shell
$ zig build-obj invalid_doc-comment.zig
/home/andy/dev/zig/doc/langref/invalid_doc-comment.zig:1:16: error: expected type expression, found 'a document comment'
/// doc-comment
               ^

unattached_doc-comment.zig
pub fn main() void {}

/// End of file
Shell
$ zig build-obj unattached_doc-comment.zig
/home/andy/dev/zig/doc/langref/unattached_doc-comment.zig:3:1: error: unattached documentation comment
/// End of file
^~~~~~~~~~~~~~~

文档注释可以与普通注释交错使用。目前,在生成包文档时,普通注释会与文档注释合并。

顶层文档注释 §

顶层文档注释是开头带有两个斜杠和一个感叹号://! 的注释;它用于记录当前模块。

如果顶层文档注释未放置在容器的开头、任何表达式之前,则会引发编译错误。

tldoc_comments.zig
//! This module provides functions for retrieving the current date and
//! time with varying degrees of precision and accuracy. It does not
//! depend on libc, but will use functions from it if available.

const S = struct {
    //! Top level comments are allowed inside a container other than a module,
    //! but it is not very useful.  Currently, when producing the package
    //! documentation, these comments are ignored.
};

§

values.zig
// Top-level declarations are order-independent:
const print = std.debug.print;
const std = @import("std");
const os = std.os;
const assert = std.debug.assert;

pub fn main() void {
    // integers
    const one_plus_one: i32 = 1 + 1;
    print("1 + 1 = {}\n", .{one_plus_one});

    // floats
    const seven_div_three: f32 = 7.0 / 3.0;
    print("7.0 / 3.0 = {}\n", .{seven_div_three});

    // boolean
    print("{}\n{}\n{}\n", .{
        true and false,
        true or false,
        !true,
    });

    // optional
    var optional_value: ?[]const u8 = null;
    assert(optional_value == null);

    print("\noptional 1\ntype: {}\nvalue: {?s}\n", .{
        @TypeOf(optional_value), optional_value,
    });

    optional_value = "hi";
    assert(optional_value != null);

    print("\noptional 2\ntype: {}\nvalue: {?s}\n", .{
        @TypeOf(optional_value), optional_value,
    });

    // error union
    var number_or_error: anyerror!i32 = error.ArgNotFound;

    print("\nerror union 1\ntype: {}\nvalue: {!}\n", .{
        @TypeOf(number_or_error),
        number_or_error,
    });

    number_or_error = 1234;

    print("\nerror union 2\ntype: {}\nvalue: {!}\n", .{
        @TypeOf(number_or_error), number_or_error,
    });
}
Shell
$ zig build-exe values.zig
$ ./values
1 + 1 = 2
7.0 / 3.0 = 2.3333333e0
false
true
false

optional 1
type: ?[]const u8
value: null

optional 2
type: ?[]const u8
value: hi

error union 1
type: anyerror!i32
value: error.ArgNotFound

error union 2
type: anyerror!i32
value: 1234

基本类型 §

基本类型
类型 C 等效 描述
i8 int8_t 有符号 8 位整数
u8 uint8_t 无符号 8 位整数
i16 int16_t 有符号 16 位整数
u16 uint16_t 无符号 16 位整数
i32 int32_t 有符号 32 位整数
u32 uint32_t 无符号 32 位整数
i64 int64_t 有符号 64 位整数
u64 uint64_t 无符号 64 位整数
i128 __int128 有符号 128 位整数
u128 unsigned __int128 无符号 128 位整数
isize intptr_t 有符号指针大小整数
usize uintptr_t, size_t 无符号指针大小整数。另请参阅 #5185
c_char char 用于与 C 语言 ABI 兼容
c_short short 用于与 C 语言 ABI 兼容
c_ushort unsigned short 用于与 C 语言 ABI 兼容
c_int int 用于与 C 语言 ABI 兼容
c_uint unsigned int 用于与 C 语言 ABI 兼容
c_long long 用于与 C 语言 ABI 兼容
c_ulong unsigned long 用于与 C 语言 ABI 兼容
c_longlong long long 用于与 C 语言 ABI 兼容
c_ulonglong unsigned long long 用于与 C 语言 ABI 兼容
c_longdouble long double 用于与 C 语言 ABI 兼容
f16 _Float16 16 位浮点数(10 位尾数)IEEE-754-2008 binary16
f32 float 32 位浮点数(23 位尾数)IEEE-754-2008 binary32
f64 double 64 位浮点数(52 位尾数)IEEE-754-2008 binary64
f80 long double 80 位浮点数(64 位尾数)IEEE-754-2008 80 位扩展精度
f128 _Float128 128 位浮点数(112 位尾数)IEEE-754-2008 binary128
bool bool truefalse
anyopaque void 用于类型擦除指针。
void (无) 始终为值 void{}
noreturn (不返回) (无) breakcontinuereturnunreachablewhile (true) {} 的类型
type (无) 类型的类型
anyerror (无) 一个错误码
comptime_int (无) 仅允许用于编译期已知的值。整数字面量的类型。
comptime_float (无) 仅允许用于编译期已知的值。浮点数字面量的类型。

除了上述整数类型,任意位宽的整数可以通过使用 iu 后跟数字的标识符来引用。例如,标识符 i7 指的是一个有符号 7 位整数。整数类型的最大允许位宽为 65535

另请参阅

基本值 §

基本值
名称 描述
truefalse bool
null 用于将可选类型设置为 null
undefined 用于使值未指定

另请参阅

字符串字面量和 Unicode 码点字面量 §

字符串字面量是常量单项指针,指向以 null 结尾的字节数组。字符串字面量的类型编码了长度以及它们以 null 结尾的事实,因此它们可以被强制转换为切片以 null 结尾的指针。解引用字符串字面量会将其转换为数组

由于 Zig 源代码是 UTF-8 编码的,源代码中字符串字面量内出现的任何非 ASCII 字节都会将其 UTF-8 含义带入 Zig 程序字符串的内容中;这些字节不会被编译器修改。可以使用 \xNN 表示法将非 UTF-8 字节嵌入到字符串字面量中。

对包含非 ASCII 字节的字符串进行索引会返回单个字节,无论其是否为有效的 UTF-8 字节。

Unicode 码点字面量的类型为 comptime_int,与整数型字面量相同。所有转义序列在字符串字面量和 Unicode 码点字面量中均有效。

string_literals.zig
const print = @import("std").debug.print;
const mem = @import("std").mem; // will be used to compare bytes

pub fn main() void {
    const bytes = "hello";
    print("{}\n", .{@TypeOf(bytes)}); // *const [5:0]u8
    print("{d}\n", .{bytes.len}); // 5
    print("{c}\n", .{bytes[1]}); // 'e'
    print("{d}\n", .{bytes[5]}); // 0
    print("{}\n", .{'e' == '\x65'}); // true
    print("{d}\n", .{'\u{1f4a9}'}); // 128169
    print("{d}\n", .{'💯'}); // 128175
    print("{u}\n", .{'⚡'});
    print("{}\n", .{mem.eql(u8, "hello", "h\x65llo")}); // true
    print("{}\n", .{mem.eql(u8, "💯", "\xf0\x9f\x92\xaf")}); // also true
    const invalid_utf8 = "\xff\xfe"; // non-UTF-8 strings are possible with \xNN notation.
    print("0x{x}\n", .{invalid_utf8[1]}); // indexing them returns individual bytes...
    print("0x{x}\n", .{"💯"[1]}); // ...as does indexing part-way through non-ASCII characters
}
Shell
$ zig build-exe string_literals.zig
$ ./string_literals
*const [5:0]u8
5
e
0
true
128169
128175
⚡
true
true
0xfe
0x9f

另请参阅

转义序列 §

转义序列
转义序列 名称
\n 换行
\r 回车
\t 制表符
\\ 反斜杠
\' 单引号
\" 双引号
\xNN 十六进制 8 位字节值(2 位数字)
\u{NNNNNN} 十六进制 Unicode 标量值 UTF-8 编码(1 位或多位数字)

请注意,最大有效的 Unicode 标量值为 0x10ffff

多行字符串字面量 §

多行字符串字面量没有转义字符,可以跨多行。要开始一个多行字符串字面量,请使用 \\ 标记。就像注释一样,字符串字面量会一直持续到行尾。行尾不包含在字符串字面量中。但是,如果下一行以 \\ 开头,则会追加一个换行符,并且字符串字面量继续。

multiline_string_literals.zig
const hello_world_in_c =
    \\#include <stdio.h>
    \\
    \\int main(int argc, char **argv) {
    \\    printf("hello world\n");
    \\    return 0;
    \\}
;

另请参阅

赋值 §

使用 const 关键字将值赋给标识符

constant_identifier_cannot_change.zig
const x = 1234;

fn foo() void {
    // It works at file scope as well as inside functions.
    const y = 5678;

    // Once assigned, an identifier cannot be changed.
    y += 1;
}

pub fn main() void {
    foo();
}
Shell
$ zig build-exe constant_identifier_cannot_change.zig
/home/andy/dev/zig/doc/langref/constant_identifier_cannot_change.zig:8:7: error: cannot assign to constant
    y += 1;
    ~~^~~~
referenced by:
    main: /home/andy/dev/zig/doc/langref/constant_identifier_cannot_change.zig:12:8
    posixCallMainAndExit: /home/andy/dev/zig/lib/std/start.zig:651:22
    4 reference(s) hidden; use '-freference-trace=6' to see all references

const 适用于标识符直接寻址的所有字节。指针拥有自己的常量性。

如果您需要一个可以修改的变量,请使用 var 关键字

mutable_var.zig
const print = @import("std").debug.print;

pub fn main() void {
    var y: i32 = 5678;

    y += 1;

    print("{d}", .{y});
}
Shell
$ zig build-exe mutable_var.zig
$ ./mutable_var
5679

变量必须被初始化

var_must_be_initialized.zig
pub fn main() void {
    var x: i32;

    x = 1;
}
Shell
$ zig build-exe var_must_be_initialized.zig
/home/andy/dev/zig/doc/langref/var_must_be_initialized.zig:2:15: error: expected '=', found ';'
    var x: i32;
              ^

undefined §

使用 undefined 使变量未初始化

assign_undefined.zig
const print = @import("std").debug.print;

pub fn main() void {
    var x: i32 = undefined;
    x = 1;
    print("{d}", .{x});
}
Shell
$ zig build-exe assign_undefined.zig
$ ./assign_undefined
1

undefined 可以被强制转换为任何类型。一旦发生这种情况,就无法再检测到该值是否为 undefinedundefined 意味着该值可以是任何东西,甚至是根据类型而言毫无意义的东西。翻译成中文,undefined 意味着“不是一个有意义的值。使用此值将是一个错误。该值将未使用,或在使用前被覆盖。”

调试模式下,Zig 会向未定义的内存写入 0xaa 字节。这是为了尽早捕获错误,并帮助在调试器中检测未定义内存的使用。然而,此行为仅是实现功能,并非语言语义,因此不保证代码能够观察到。

解构 §

解构赋值可以将可索引的聚合类型(元组数组向量)的元素分离出来

destructuring_to_existing.zig
const print = @import("std").debug.print;

pub fn main() void {
    var x: u32 = undefined;
    var y: u32 = undefined;
    var z: u32 = undefined;

    const tuple = .{ 1, 2, 3 };

    x, y, z = tuple;

    print("tuple: x = {}, y = {}, z = {}\n", .{x, y, z});

    const array = [_]u32{ 4, 5, 6 };

    x, y, z = array;

    print("array: x = {}, y = {}, z = {}\n", .{x, y, z});

    const vector: @Vector(3, u32) = .{ 7, 8, 9 };

    x, y, z = vector;

    print("vector: x = {}, y = {}, z = {}\n", .{x, y, z});
}
Shell
$ zig build-exe destructuring_to_existing.zig
$ ./destructuring_to_existing
tuple: x = 1, y = 2, z = 3
array: x = 4, y = 5, z = 6
vector: x = 7, y = 8, z = 9

解构表达式只能出现在代码块内(即不能在容器作用域内)。赋值的左侧必须由逗号分隔的列表组成,其中每个元素可以是左值(例如,一个已存在的 `var`)或一个变量声明

destructuring_mixed.zig
const print = @import("std").debug.print;

pub fn main() void {
    var x: u32 = undefined;

    const tuple = .{ 1, 2, 3 };

    x, var y : u32, const z = tuple;

    print("x = {}, y = {}, z = {}\n", .{x, y, z});

    // y is mutable
    y = 100;

    // You can use _ to throw away unwanted values.
    _, x, _ = tuple;

    print("x = {}", .{x});
}
Shell
$ zig build-exe destructuring_mixed.zig
$ ./destructuring_mixed
x = 1, y = 2, z = 3
x = 2

解构表达式可以加上 comptime 关键字前缀,在这种情况下,整个解构表达式会在编译期求值。所有声明的 var 都将是 comptime var,并且所有表达式(包括结果位置和赋值表达式)都在编译期求值。

另请参阅

Zig 测试 §

在一个或多个 test 声明中编写的代码可用于确保行为符合预期

testing_introduction.zig
const std = @import("std");

test "expect addOne adds one to 41" {

    // The Standard Library contains useful functions to help create tests.
    // `expect` is a function that verifies its argument is true.
    // It will return an error if its argument is false to indicate a failure.
    // `try` is used to return an error to the test runner to notify it that the test failed.
    try std.testing.expect(addOne(41) == 42);
}

test addOne {
    // A test name can also be written using an identifier.
    // This is a doctest, and serves as documentation for `addOne`.
    try std.testing.expect(addOne(41) == 42);
}

/// The function `addOne` adds one to the number given as its argument.
fn addOne(number: i32) i32 {
    return number + 1;
}
Shell
$ zig test testing_introduction.zig
1/2 testing_introduction.test.expect addOne adds one to 41...OK
2/2 testing_introduction.decltest.addOne...OK
All 2 tests passed.

代码示例 testing_introduction.zig 测试了函数 addOne,以确保在给定输入 41 时,它返回 42。从这个测试的角度来看,addOne 函数被称为被测代码

zig test 是一个创建和运行测试构建的工具。默认情况下,它使用 Zig 标准库提供的默认测试运行器作为其主入口点,构建并运行一个可执行程序。在构建过程中,在解析给定 Zig 源文件时找到的 test 声明会被包含进来,供默认测试运行器运行和报告。

上面显示的 shell 输出在 zig test 命令后显示了两行。这些行是由默认测试运行器打印到标准错误输出的:

1/2 testing_introduction.test.expect addOne adds one to 41...
这样的行表示正在运行的是总测试数中的哪一个测试。在这种情况下,1/2 表示正在运行的是总共两个测试中的第一个。请注意,当测试运行器程序的标准错误输出到终端时,这些行会在测试成功时被清除。
2/2 testing_introduction.decltest.addOne...
当测试名称是标识符时,默认测试运行器使用文本 decltest 而不是 test。
所有 2 个测试通过。
此行表示已通过的测试总数。

测试声明 §

测试声明包含关键字 test,后跟一个可选名称,该名称可以写成字符串字面量标识符,再后跟一个代码块,其中包含函数中允许的任何有效 Zig 代码。

非命名测试块总是在测试构建期间运行,并且不受跳过测试的限制。

测试声明类似于函数:它们有一个返回类型和一个代码块。test 的隐式返回类型是错误联合体类型 anyerror!void,并且不能更改。当 Zig 源文件不使用 zig test 工具构建时,测试声明将从构建中省略。

测试声明可以写在与被测代码相同的文件中,也可以写在单独的 Zig 源文件中。由于测试声明是顶层声明,它们与顺序无关,可以写在被测代码之前或之后。

另请参阅

文档测试 (Doctests) §

使用标识符命名的测试声明是文档测试(doctests)。该标识符必须引用作用域内的另一个声明。文档测试,就像文档注释一样,作为相关声明的文档,并将出现在该声明生成的文档中。

一个有效的文档测试应该是自包含的,并专注于被测试的声明,回答新用户可能对其接口或预期用法提出的问题,同时避免不必要或令人困惑的细节。文档测试不是文档注释的替代品,而是提供一个可测试、代码驱动的示例的补充和伴侣,并通过 zig test 进行验证。

测试失败 §

默认测试运行器会检查测试返回的错误。当测试返回错误时,该测试被视为失败,其错误返回追踪将输出到标准错误。所有测试运行完毕后,将报告失败的总数。

testing_failure.zig
const std = @import("std");

test "expect this to fail" {
    try std.testing.expect(false);
}

test "expect this to succeed" {
    try std.testing.expect(true);
}
Shell
$ zig test testing_failure.zig
1/2 testing_failure.test.expect this to fail...FAIL (TestUnexpectedResult)
/home/andy/dev/zig/lib/std/testing.zig:580:14: 0x104890f in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/home/andy/dev/zig/doc/langref/testing_failure.zig:4:5: 0x10489a5 in test.expect this to fail (test)
    try std.testing.expect(false);
    ^
2/2 testing_failure.test.expect this to succeed...OK
1 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/home/andy/dev/zig/.zig-cache/o/37b2473244f3a3046525f6f7b1b36e1a/test --seed=0xd1a8ca45

跳过测试 §

跳过测试的一种方法是使用 zig test 命令行参数 --test-filter [text] 来过滤掉它们。这使得测试构建只包含名称中包含所提供过滤文本的测试。请注意,即使使用 --test-filter [text] 命令行参数,非命名测试也会运行。

要以编程方式跳过测试,请让 test 返回错误 error.SkipZigTest,默认测试运行器将认为该测试已被跳过。所有测试运行完毕后,将报告跳过的测试总数。

testing_skip.zig
test "this will be skipped" {
    return error.SkipZigTest;
}
Shell
$ zig test testing_skip.zig
1/1 testing_skip.test.this will be skipped...SKIP
0 passed; 1 skipped; 0 failed.

报告内存泄漏 §

当代码使用 Zig 标准库的测试分配器 std.testing.allocator 分配内存时,默认测试运行器将报告使用该测试分配器发现的任何内存泄漏。

testing_detect_leak.zig
const std = @import("std");

test "detect leak" {
    var list = std.ArrayList(u21).init(std.testing.allocator);
    // missing `defer list.deinit();`
    try list.append('☔');

    try std.testing.expect(list.items.len == 1);
}
Shell
$ zig test testing_detect_leak.zig
1/1 testing_detect_leak.test.detect leak...OK
[gpa] (err): memory address 0x7ff59fee0000 leaked:
/home/andy/dev/zig/lib/std/array_list.zig:474:67: 0x10696b2 in ensureTotalCapacityPrecise (test)
                const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
                                                                  ^
/home/andy/dev/zig/lib/std/array_list.zig:450:51: 0x104eb90 in ensureTotalCapacity (test)
            return self.ensureTotalCapacityPrecise(better_capacity);
                                                  ^
/home/andy/dev/zig/lib/std/array_list.zig:500:41: 0x104cebf in addOne (test)
            try self.ensureTotalCapacity(newlen);
                                        ^
/home/andy/dev/zig/lib/std/array_list.zig:261:49: 0x104a9dd in append (test)
            const new_item_ptr = try self.addOne();
                                                ^
/home/andy/dev/zig/doc/langref/testing_detect_leak.zig:6:20: 0x1048c75 in test.detect leak (test)
    try list.append('☔');
                   ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10f7f25 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10f1f6d in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10f13e2 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10f0fbd in _start (test)
    asm volatile (switch (native_arch) {
    ^

All 1 tests passed.
1 errors were logged.
1 tests leaked memory.
error: the following test command failed with exit code 1:
/home/andy/dev/zig/.zig-cache/o/ce49f9d6b44eb0d79178a6c6bd6f2b47/test --seed=0x507d4597

另请参阅

检测测试构建 §

使用编译变量 @import("builtin").is_test 来检测是否为测试构建

testing_detect_test.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;

test "builtin.is_test" {
    try expect(isATest());
}

fn isATest() bool {
    return builtin.is_test;
}
Shell
$ zig test testing_detect_test.zig
1/1 testing_detect_test.test.builtin.is_test...OK
All 1 tests passed.

测试输出和日志 §

默认测试运行器和 Zig 标准库的测试命名空间将消息输出到标准错误流。

测试命名空间 §

Zig 标准库的 testing 命名空间包含有用的函数,可帮助您创建测试。除了 expect 函数之外,本文档还使用了一些其他函数,示例如下:

testing_namespace.zig
const std = @import("std");

test "expectEqual demo" {
    const expected: i32 = 42;
    const actual = 42;

    // The first argument to `expectEqual` is the known, expected, result.
    // The second argument is the result of some expression.
    // The actual's type is casted to the type of expected.
    try std.testing.expectEqual(expected, actual);
}

test "expectError demo" {
    const expected_error = error.DemoError;
    const actual_error_union: anyerror!void = error.DemoError;

    // `expectError` will fail when the actual error is different than
    // the expected error.
    try std.testing.expectError(expected_error, actual_error_union);
}
Shell
$ zig test testing_namespace.zig
1/2 testing_namespace.test.expectEqual demo...OK
2/2 testing_namespace.test.expectError demo...OK
All 2 tests passed.

Zig 标准库还包含用于比较切片、字符串等的函数。有关更多可用函数,请参阅Zig 标准库std.testing 命名空间的其他部分。

测试工具文档 §

zig test 有几个影响编译的命令行参数。有关完整列表,请参阅 zig test --help

变量 §

变量是内存存储的单元。

声明变量时,通常更倾向于使用 const 而不是 var。这减少了人类和计算机在阅读代码时的工作量,并创造了更多的优化机会。

extern 关键字或 @extern 内置函数可用于链接另一个对象导出的变量。export 关键字或 @export 内置函数可用于在链接时使变量可供其他对象使用。在这两种情况下,变量的类型都必须与 C ABI 兼容。

另请参阅

标识符 §

变量标识符绝不允许遮蔽外部作用域中的标识符。

标识符必须以字母字符或下划线开头,后可跟任意数量的字母数字字符或下划线。它们不得与任何关键字重叠。请参阅关键字参考

如果需要一个不符合这些要求的名称,例如用于与外部库链接,可以使用 @"" 语法。

identifiers.zig
const @"identifier with spaces in it" = 0xff;
const @"1SmallStep4Man" = 112358;

const c = @import("std").c;
pub extern "c" fn @"error"() void;
pub extern "c" fn @"fstat$INODE64"(fd: c.fd_t, buf: *c.Stat) c_int;

const Color = enum {
    red,
    @"really red",
};
const color: Color = .@"really red";

容器级别变量 §

容器级别变量具有静态生命周期,并且与顺序无关,惰性分析。容器级别变量的初始值隐式为编译期。如果容器级别变量是 const,则其值为编译期已知;否则,它在运行时已知。

test_container_level_variables.zig
var y: i32 = add(10, x);
const x: i32 = add(12, 34);

test "container level variables" {
    try expect(x == 46);
    try expect(y == 56);
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}

const std = @import("std");
const expect = std.testing.expect;
Shell
$ zig test test_container_level_variables.zig
1/1 test_container_level_variables.test.container level variables...OK
All 1 tests passed.

容器级别变量可以在 structunionenumopaque 内部声明

test_namespaced_container_level_variable.zig
const std = @import("std");
const expect = std.testing.expect;

test "namespaced container level variable" {
    try expect(foo() == 1235);
    try expect(foo() == 1236);
}

const S = struct {
    var x: i32 = 1234;
};

fn foo() i32 {
    S.x += 1;
    return S.x;
}
Shell
$ zig test test_namespaced_container_level_variable.zig
1/1 test_namespaced_container_level_variable.test.namespaced container level variable...OK
All 1 tests passed.

静态局部变量 §

通过在函数内部使用容器,也可以拥有具有静态生命周期的局部变量。

test_static_local_variable.zig
const std = @import("std");
const expect = std.testing.expect;

test "static local variable" {
    try expect(foo() == 1235);
    try expect(foo() == 1236);
}

fn foo() i32 {
    const S = struct {
        var x: i32 = 1234;
    };
    S.x += 1;
    return S.x;
}
Shell
$ zig test test_static_local_variable.zig
1/1 test_static_local_variable.test.static local variable...OK
All 1 tests passed.

线程局部变量 §

可以使用 threadlocal 关键字将变量指定为线程局部变量,这使得每个线程使用该变量的独立实例

test_thread_local_variables.zig
const std = @import("std");
const assert = std.debug.assert;

threadlocal var x: i32 = 1234;

test "thread local storage" {
    const thread1 = try std.Thread.spawn(.{}, testTls, .{});
    const thread2 = try std.Thread.spawn(.{}, testTls, .{});
    testTls();
    thread1.join();
    thread2.join();
}

fn testTls() void {
    assert(x == 1234);
    x += 1;
    assert(x == 1235);
}
Shell
$ zig test test_thread_local_variables.zig
1/1 test_thread_local_variables.test.thread local storage...OK
All 1 tests passed.

对于单线程构建,所有线程局部变量都被视为常规容器级别变量

线程局部变量不能是 const

局部变量 §

局部变量出现在函数编译期代码块和@cImport代码块内部。

当局部变量是 const 时,这意味着初始化后,变量的值不会改变。如果 const 变量的初始值是编译期已知的,那么该变量也是编译期已知的。

局部变量可以用 comptime 关键字进行限定。这使得变量的值在编译期已知,并且对变量的所有加载和存储都在程序的语义分析期间发生,而不是在运行时。在 comptime 表达式中声明的所有变量都是隐式编译期变量。

test_comptime_variables.zig
const std = @import("std");
const expect = std.testing.expect;

test "comptime vars" {
    var x: i32 = 1;
    comptime var y: i32 = 1;

    x += 1;
    y += 1;

    try expect(x == 2);
    try expect(y == 2);

    if (y != 2) {
        // This compile error never triggers because y is a comptime variable,
        // and so `y != 2` is a comptime value, and this if is statically evaluated.
        @compileError("wrong y value");
    }
}
Shell
$ zig test test_comptime_variables.zig
1/1 test_comptime_variables.test.comptime vars...OK
All 1 tests passed.

整数 §

整数型字面量 §

integer_literals.zig
const decimal_int = 98222;
const hex_int = 0xff;
const another_hex_int = 0xFF;
const octal_int = 0o755;
const binary_int = 0b11110000;

// underscores may be placed between two digits as a visual separator
const one_billion = 1_000_000_000;
const binary_mask = 0b1_1111_1111;
const permissions = 0o7_5_5;
const big_address = 0xFF80_0000_0000_0000;

运行时整数值 §

整数型字面量没有大小限制,如果发生任何非法行为,编译器会捕获它。

然而,一旦整数值不再在编译期已知,它必须具有已知大小,并且容易受到安全检查的非法行为的影响。

runtime_vs_comptime.zig
fn divide(a: i32, b: i32) i32 {
    return a / b;
}

在此函数中,值 ab 仅在运行时已知,因此此除法操作容易受到整数溢出除以零的影响。

诸如 +- 等运算符在整数溢出时会导致非法行为。所有目标都提供了用于环绕和饱和算术的替代运算符。+%-% 执行环绕算术,而 +|-| 执行饱和算术。

Zig 支持任意位宽的整数,通过使用 iu 后跟数字的标识符来引用。例如,标识符 i7 指的是一个有符号 7 位整数。整数类型的最大允许位宽为 65535。对于有符号整数类型,Zig 使用二进制补码表示。

另请参阅

浮点数 §

Zig 有以下浮点类型:

  • f16 - IEEE-754-2008 binary16
  • f32 - IEEE-754-2008 binary32
  • f64 - IEEE-754-2008 binary64
  • f80 - IEEE-754-2008 80 位扩展精度
  • f128 - IEEE-754-2008 binary128
  • c_longdouble - 与目标 C ABI 的 long double 匹配

浮点数字面量 §

浮点数字面量的类型为 comptime_float,它保证具有与最大的其他浮点类型(即 f128)相同的精度和操作。

浮点数字面量强制转换为任何浮点类型,并且在没有小数部分时,可以强制转换为任何整数类型。

float_literals.zig
const floating_point = 123.0E+77;
const another_float = 123.0;
const yet_another = 123.0e+77;

const hex_floating_point = 0x103.70p-5;
const another_hex_float = 0x103.70;
const yet_another_hex_float = 0x103.70P-5;

// underscores may be placed between two digits as a visual separator
const lightspeed = 299_792_458.000_000;
const nanosecond = 0.000_000_001;
const more_hex = 0x1234_5678.9ABC_CDEFp-10;

没有用于 NaN、无穷大或负无穷大的语法。对于这些特殊值,必须使用标准库:

float_special_values.zig
const std = @import("std");

const inf = std.math.inf(f32);
const negative_inf = -std.math.inf(f64);
const nan = std.math.nan(f128);

浮点数运算 §

默认情况下,浮点运算使用 Strict 模式,但您可以按代码块切换到 Optimized 模式

float_mode_obj.zig
const std = @import("std");
const big = @as(f64, 1 << 40);

export fn foo_strict(x: f64) f64 {
    return x + big - big;
}

export fn foo_optimized(x: f64) f64 {
    @setFloatMode(.optimized);
    return x + big - big;
}
Shell
$ zig build-obj float_mode_obj.zig -O ReleaseFast

对于此测试,我们必须将代码分离到两个目标文件——否则优化器会在编译时计算出所有值,而编译时操作是在严格模式下进行的。

float_mode_exe.zig
const print = @import("std").debug.print;

extern fn foo_strict(x: f64) f64;
extern fn foo_optimized(x: f64) f64;

pub fn main() void {
    const x = 0.001;
    print("optimized = {}\n", .{foo_optimized(x)});
    print("strict = {}\n", .{foo_strict(x)});
}

另请参阅

运算符 §

没有运算符重载。当您在 Zig 中看到一个运算符时,您就知道它正在执行此表中的某个操作,仅此而已。

运算符表格 §

名称 语法 类型 备注 示例
加法
a + b
a += b
2 + 5 == 7
环绕加法
a +% b
a +%= b
@as(u32, 0xffffffff) +% 1 == 0
饱和加法
a +| b
a +|= b
@as(u8, 255) +| 1 == @as(u8, 255)
减法
a - b
a -= b
2 - 5 == -3
环绕减法
a -% b
a -%= b
@as(u8, 0) -% 1 == 255
饱和减法
a -| b
a -|= b
@as(u32, 0) -| 1 == 0
求反
-a
-1 == 0 - 1
环绕求反
-%a
  • 二进制补码环绕行为。
-%@as(i8, -128) == -128
乘法
a * b
a *= b
2 * 5 == 10
环绕乘法
a *% b
a *%= b
@as(u8, 200) *% 2 == 144
饱和乘法
a *| b
a *|= b
@as(u8, 200) *| 2 == 255
除法
a / b
a /= b
10 / 5 == 2
取余除法
a % b
a %= b
10 % 3 == 1
左移位
a << b
a <<= b
  • 将所有位向左移动,在最低有效位插入新的零。
  • b 必须是编译期已知的,或者其类型具有与 a 相同的 log2 位数。
  • 另请参阅 @shlExact
  • 另请参阅 @shlWithOverflow
0b1 << 8 == 0b100000000
饱和左移位
a <<| b
a <<|= b
@as(u8, 1) <<| 8 == 255
右移位
a >> b
a >>= b
  • 将所有位向右移动,在最高有效位插入零。
  • b 必须是编译期已知的,或者其类型具有与 a 相同的 log2 位数。
  • 另请参阅 @shrExact
0b1010 >> 1 == 0b101
按位与
a & b
a &= b
0b011 & 0b101 == 0b001
按位或
a | b
a |= b
0b010 | 0b100 == 0b110
按位异或
a ^ b
a ^= b
0b011 ^ 0b101 == 0b110
按位非
~a
~@as(u8, 0b10101111) == 0b01010000
默认可选解包
a orelse b
如果 anull,则返回 b(“默认值”),否则返回 a 的解包值。请注意,b 的类型可能为 noreturn
const value: ?u32 = null;
const unwrapped = value orelse 1234;
unwrapped == 1234
可选解包
a.?
等价于
a orelse unreachable
const value: ?u32 = 5678;
value.? == 5678
默认错误解包
a catch b
a catch |err| b
如果 a 是一个 error,则返回 b(“默认值”),否则返回 a 的解包值。请注意,b 的类型可能为 noreturnerrerror 并且在表达式 b 的作用域内。
const value: anyerror!u32 = error.Broken;
const unwrapped = value catch 1234;
unwrapped == 1234
逻辑与
a and b
如果 afalse,则返回 false,而不评估 b。否则,返回 b
(false and true) == false
逻辑或
a or b
如果 atrue,则返回 true,而不评估 b。否则,返回 b
(false or true) == true
逻辑非
!a
!false == true
相等
a == b
如果 a 和 b 相等,则返回 true,否则返回 false。对操作数调用对等类型解析
(1 == 1) == true
Null 检查
a == null
如果 a 为 null,则返回 true,否则返回 false
const value: ?u32 = null;
(value == null) == true
不等
a != b
如果 a 和 b 相等,则返回 false,否则返回 true。对操作数调用对等类型解析
(1 != 1) == false
非 Null 检查
a != null
如果 a 为 null,则返回 false,否则返回 true
const value: ?u32 = null;
(value != null) == false
大于
a > b
如果 a 大于 b,则返回 true,否则返回 false。对操作数调用对等类型解析
(2 > 1) == true
大于或等于
a >= b
如果 a 大于或等于 b,则返回 true,否则返回 false。对操作数调用对等类型解析
(2 >= 1) == true
小于
a < b
如果 a 小于 b,则返回 true,否则返回 false。对操作数调用对等类型解析
(1 < 2) == true
小于或等于
a <= b
如果 a 小于或等于 b,则返回 true,否则返回 false。对操作数调用对等类型解析
(1 <= 2) == true
数组连接
a ++ b
const mem = @import("std").mem;
const array1 = [_]u32{1,2};
const array2 = [_]u32{3,4};
const together = array1 ++ array2;
mem.eql(u32, &together, &[_]u32{1,2,3,4})
数组乘法
a ** b
const mem = @import("std").mem;
const pattern = "ab" ** 3;
mem.eql(u8, pattern, "ababab")
指针解引用
a.*
指针解引用。
const x: u32 = 1234;
const ptr = &x;
ptr.* == 1234
取地址
&a
所有类型
const x: u32 = 1234;
const ptr = &x;
ptr.* == 1234
错误集合并
a || b
合并错误集
const A = error{One};
const B = error{Two};
(A || B) == error{One, Two}

优先级 §

x() x[] x.y x.* x.?
a!b
x{}
!x -x -%x ~x &x ?x
* / % ** *% *| ||
+ - ++ +% -% +| -|
<< >> <<|
& ^ | orelse catch
== != < > <= >=
and
or
= *= *%= *|= /= %= += +%= +|= -= -%= -|= <<= <<|= >>= &= ^= |=

数组 §

test_arrays.zig
const expect = @import("std").testing.expect;
const assert = @import("std").debug.assert;
const mem = @import("std").mem;

// array literal
const message = [_]u8{ 'h', 'e', 'l', 'l', 'o' };

// alternative initialization using result location
const alt_message: [5]u8 = .{ 'h', 'e', 'l', 'l', 'o' };

comptime {
    assert(mem.eql(u8, &message, &alt_message));
}

// get the size of an array
comptime {
    assert(message.len == 5);
}

// A string literal is a single-item pointer to an array.
const same_message = "hello";

comptime {
    assert(mem.eql(u8, &message, same_message));
}

test "iterate over an array" {
    var sum: usize = 0;
    for (message) |byte| {
        sum += byte;
    }
    try expect(sum == 'h' + 'e' + 'l' * 2 + 'o');
}

// modifiable array
var some_integers: [100]i32 = undefined;

test "modify an array" {
    for (&some_integers, 0..) |*item, i| {
        item.* = @intCast(i);
    }
    try expect(some_integers[10] == 10);
    try expect(some_integers[99] == 99);
}

// array concatenation works if the values are known
// at compile time
const part_one = [_]i32{ 1, 2, 3, 4 };
const part_two = [_]i32{ 5, 6, 7, 8 };
const all_of_it = part_one ++ part_two;
comptime {
    assert(mem.eql(i32, &all_of_it, &[_]i32{ 1, 2, 3, 4, 5, 6, 7, 8 }));
}

// remember that string literals are arrays
const hello = "hello";
const world = "world";
const hello_world = hello ++ " " ++ world;
comptime {
    assert(mem.eql(u8, hello_world, "hello world"));
}

// ** does repeating patterns
const pattern = "ab" ** 3;
comptime {
    assert(mem.eql(u8, pattern, "ababab"));
}

// initialize an array to zero
const all_zero = [_]u16{0} ** 10;

comptime {
    assert(all_zero.len == 10);
    assert(all_zero[5] == 0);
}

// use compile-time code to initialize an array
var fancy_array = init: {
    var initial_value: [10]Point = undefined;
    for (&initial_value, 0..) |*pt, i| {
        pt.* = Point{
            .x = @intCast(i),
            .y = @intCast(i * 2),
        };
    }
    break :init initial_value;
};
const Point = struct {
    x: i32,
    y: i32,
};

test "compile-time array initialization" {
    try expect(fancy_array[4].x == 4);
    try expect(fancy_array[4].y == 8);
}

// call a function to initialize an array
var more_points = [_]Point{makePoint(3)} ** 10;
fn makePoint(x: i32) Point {
    return Point{
        .x = x,
        .y = x * 2,
    };
}
test "array initialization with function calls" {
    try expect(more_points[4].x == 3);
    try expect(more_points[4].y == 6);
    try expect(more_points.len == 10);
}
Shell
$ zig test test_arrays.zig
1/4 test_arrays.test.iterate over an array...OK
2/4 test_arrays.test.modify an array...OK
3/4 test_arrays.test.compile-time array initialization...OK
4/4 test_arrays.test.array initialization with function calls...OK
All 4 tests passed.

另请参阅

多维数组 §

多维数组可以通过嵌套数组来创建

test_multidimensional_arrays.zig
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;

const mat4x5 = [4][5]f32{
    [_]f32{ 1.0, 0.0, 0.0, 0.0, 0.0 },
    [_]f32{ 0.0, 1.0, 0.0, 1.0, 0.0 },
    [_]f32{ 0.0, 0.0, 1.0, 0.0, 0.0 },
    [_]f32{ 0.0, 0.0, 0.0, 1.0, 9.9 },
};
test "multidimensional arrays" {
    // mat4x5 itself is a one-dimensional array of arrays.
    try expectEqual(mat4x5[1], [_]f32{ 0.0, 1.0, 0.0, 1.0, 0.0 });

    // Access the 2D array by indexing the outer array, and then the inner array.
    try expect(mat4x5[3][4] == 9.9);

    // Here we iterate with for loops.
    for (mat4x5, 0..) |row, row_index| {
        for (row, 0..) |cell, column_index| {
            if (row_index == column_index) {
                try expect(cell == 1.0);
            }
        }
    }

    // Initialize a multidimensional array to zeros.
    const all_zero: [4][5]f32 = .{.{0} ** 5} ** 4;
    try expect(all_zero[0][0] == 0);
}
Shell
$ zig test test_multidimensional_arrays.zig
1/1 test_multidimensional_arrays.test.multidimensional arrays...OK
All 1 tests passed.

哨兵终止数组 §

语法 [N:x]T 描述了一个数组,它在与长度 N 对应的索引处有一个值为 x 的哨兵元素。

test_null_terminated_array.zig
const std = @import("std");
const expect = std.testing.expect;

test "0-terminated sentinel array" {
    const array = [_:0]u8{ 1, 2, 3, 4 };

    try expect(@TypeOf(array) == [4:0]u8);
    try expect(array.len == 4);
    try expect(array[4] == 0);
}

test "extra 0s in 0-terminated sentinel array" {
    // The sentinel value may appear earlier, but does not influence the compile-time 'len'.
    const array = [_:0]u8{ 1, 0, 0, 4 };

    try expect(@TypeOf(array) == [4:0]u8);
    try expect(array.len == 4);
    try expect(array[4] == 0);
}
Shell
$ zig test test_null_terminated_array.zig
1/2 test_null_terminated_array.test.0-terminated sentinel array...OK
2/2 test_null_terminated_array.test.extra 0s in 0-terminated sentinel array...OK
All 2 tests passed.

另请参阅

数组解构 §

数组可以被解构

destructuring_arrays.zig
const print = @import("std").debug.print;

fn swizzleRgbaToBgra(rgba: [4]u8) [4]u8 {
    // readable swizzling by destructuring
    const r, const g, const b, const a = rgba;
    return .{ b, g, r, a };
}

pub fn main() void {
    const pos = [_]i32{ 1, 2 };
    const x, const y = pos;
    print("x = {}, y = {}\n", .{x, y});

    const orange: [4]u8 = .{ 255, 165, 0, 255 };
    print("{any}\n", .{swizzleRgbaToBgra(orange)});
}
Shell
$ zig build-exe destructuring_arrays.zig
$ ./destructuring_arrays
x = 1, y = 2
{ 0, 165, 255, 255 }

另请参阅

向量 §

向量是一组布尔值、整数浮点数指针,它们尽可能地并行操作,使用 SIMD 指令。向量类型使用内置函数 @Vector 创建。

向量支持与其底层基本类型相同的内置运算符。这些操作是逐元素执行的,并返回与输入向量相同长度的向量。这包括:

  • 算术运算 (+, -, /, *, @divFloor, @sqrt, @ceil, @log 等)
  • 位运算符 (>>, <<, &, |, ~ 等)
  • 比较运算符 (<, >, == 等)

禁止对标量(单个数字)和向量的混合使用数学运算符。Zig 提供了 @splat 内置函数,可轻松地从标量转换为向量,并且它支持 @reduce 和数组索引语法,可将向量转换为标量。向量还支持与编译时已知长度的定长数组进行赋值。

为了在向量内部和向量之间重新排列元素,Zig 提供了 @shuffle@select 函数。

对短于目标机器原生 SIMD 大小的向量进行操作,通常会编译成单个 SIMD 指令,而长于目标机器原生 SIMD 大小的向量,则会编译成多个 SIMD 指令。如果给定操作在目标架构上没有 SIMD 支持,编译器将默认逐个向量元素进行操作。Zig 支持任何编译期已知的向量长度,最大可达 2^32-1,尽管通常以 2 的小幂次(2-64)最为常见。请注意,过长的向量长度(例如 2^20)可能会导致当前 Zig 版本的编译器崩溃。

test_vector.zig
const std = @import("std");
const expectEqual = std.testing.expectEqual;

test "Basic vector usage" {
    // Vectors have a compile-time known length and base type.
    const a = @Vector(4, i32){ 1, 2, 3, 4 };
    const b = @Vector(4, i32){ 5, 6, 7, 8 };

    // Math operations take place element-wise.
    const c = a + b;

    // Individual vector elements can be accessed using array indexing syntax.
    try expectEqual(6, c[0]);
    try expectEqual(8, c[1]);
    try expectEqual(10, c[2]);
    try expectEqual(12, c[3]);
}

test "Conversion between vectors, arrays, and slices" {
    // Vectors and fixed-length arrays can be automatically assigned back and forth
    const arr1: [4]f32 = [_]f32{ 1.1, 3.2, 4.5, 5.6 };
    const vec: @Vector(4, f32) = arr1;
    const arr2: [4]f32 = vec;
    try expectEqual(arr1, arr2);

    // You can also assign from a slice with comptime-known length to a vector using .*
    const vec2: @Vector(2, f32) = arr1[1..3].*;

    const slice: []const f32 = &arr1;
    var offset: u32 = 1; // var to make it runtime-known
    _ = &offset; // suppress 'var is never mutated' error
    // To extract a comptime-known length from a runtime-known offset,
    // first extract a new slice from the starting offset, then an array of
    // comptime-known length
    const vec3: @Vector(2, f32) = slice[offset..][0..2].*;
    try expectEqual(slice[offset], vec2[0]);
    try expectEqual(slice[offset + 1], vec2[1]);
    try expectEqual(vec2, vec3);
}
Shell
$ zig test test_vector.zig
1/2 test_vector.test.Basic vector usage...OK
2/2 test_vector.test.Conversion between vectors, arrays, and slices...OK
All 2 tests passed.

TODO 讨论 C ABI 互操作性
TODO 考虑建议使用 std.MultiArrayList

另请参阅

向量解构 §

向量可以被解构

destructuring_vectors.zig
const print = @import("std").debug.print;

// emulate punpckldq
pub fn unpack(x: @Vector(4, f32), y: @Vector(4, f32)) @Vector(4, f32) {
    const a, const c, _, _ = x;
    const b, const d, _, _ = y;
    return .{ a, b, c, d };
}

pub fn main() void {
    const x: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 };
    const y: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 };
    print("{}", .{unpack(x, y)});
}
Shell
$ zig build-exe destructuring_vectors.zig
$ ./destructuring_vectors
{ 1e0, 5e0, 2e0, 6e0 }

另请参阅

指针 §

Zig 有两种指针:单项指针和多项指针。

  • *T - 指向一个单项的指针。
    • 支持解引用语法:ptr.*
    • 支持切片语法:ptr[0..1]
    • 支持指针减法:ptr - ptr
  • [*]T - 指向未知数量项的多项指针。
    • 支持索引语法:ptr[i]
    • 支持切片语法:ptr[start..end]ptr[start..]
    • 支持指针-整数算术:ptr + int, ptr - int
    • 支持指针减法:ptr - ptr
    T 必须具有已知大小,这意味着它不能是 anyopaque 或任何其他不透明类型

这些类型与数组切片密切相关

  • *[N]T - 指向 N 个项的指针,与指向数组的单项指针相同。
    • 支持索引语法:array_ptr[i]
    • 支持切片语法:array_ptr[start..end]
    • 支持 len 属性:array_ptr.len
    • 支持指针减法:array_ptr - array_ptr
  • []T - 是一个切片(一个胖指针,其中包含一个 [*]T 类型的指针和一个长度)。
    • 支持索引语法:slice[i]
    • 支持切片语法:slice[start..end]
    • 支持 len 属性:slice.len

使用 &x 获取单项指针

test_single_item_pointer.zig
const expect = @import("std").testing.expect;

test "address of syntax" {
    // Get the address of a variable:
    const x: i32 = 1234;
    const x_ptr = &x;

    // Dereference a pointer:
    try expect(x_ptr.* == 1234);

    // When you get the address of a const variable, you get a const single-item pointer.
    try expect(@TypeOf(x_ptr) == *const i32);

    // If you want to mutate the value, you'd need an address of a mutable variable:
    var y: i32 = 5678;
    const y_ptr = &y;
    try expect(@TypeOf(y_ptr) == *i32);
    y_ptr.* += 1;
    try expect(y_ptr.* == 5679);
}

test "pointer array access" {
    // Taking an address of an individual element gives a
    // single-item pointer. This kind of pointer
    // does not support pointer arithmetic.
    var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    const ptr = &array[2];
    try expect(@TypeOf(ptr) == *u8);

    try expect(array[2] == 3);
    ptr.* += 1;
    try expect(array[2] == 4);
}

test "slice syntax" {
    // Get a pointer to a variable:
    var x: i32 = 1234;
    const x_ptr = &x;

    // Convert to array pointer using slice syntax:
    const x_array_ptr = x_ptr[0..1];
    try expect(@TypeOf(x_array_ptr) == *[1]i32);

    // Coerce to many-item pointer:
    const x_many_ptr: [*]i32 = x_array_ptr;
    try expect(x_many_ptr[0] == 1234);
}
Shell
$ zig test test_single_item_pointer.zig
1/3 test_single_item_pointer.test.address of syntax...OK
2/3 test_single_item_pointer.test.pointer array access...OK
3/3 test_single_item_pointer.test.slice syntax...OK
All 3 tests passed.

Zig 支持指针算术。最好将指针赋值给 [*]T 并增加该变量。例如,直接增加切片中的指针会破坏它。

test_pointer_arithmetic.zig
const expect = @import("std").testing.expect;

test "pointer arithmetic with many-item pointer" {
    const array = [_]i32{ 1, 2, 3, 4 };
    var ptr: [*]const i32 = &array;

    try expect(ptr[0] == 1);
    ptr += 1;
    try expect(ptr[0] == 2);

    // slicing a many-item pointer without an end is equivalent to
    // pointer arithmetic: `ptr[start..] == ptr + start`
    try expect(ptr[1..] == ptr + 1);

    // subtraction between any two pointers except slices based on element size is supported
    try expect(&ptr[1] - &ptr[0] == 1);
}

test "pointer arithmetic with slices" {
    var array = [_]i32{ 1, 2, 3, 4 };
    var length: usize = 0; // var to make it runtime-known
    _ = &length; // suppress 'var is never mutated' error
    var slice = array[length..array.len];

    try expect(slice[0] == 1);
    try expect(slice.len == 4);

    slice.ptr += 1;
    // now the slice is in an bad state since len has not been updated

    try expect(slice[0] == 2);
    try expect(slice.len == 4);
}
Shell
$ zig test test_pointer_arithmetic.zig
1/2 test_pointer_arithmetic.test.pointer arithmetic with many-item pointer...OK
2/2 test_pointer_arithmetic.test.pointer arithmetic with slices...OK
All 2 tests passed.

在 Zig 中,我们通常更倾向于切片而不是哨兵终止指针。您可以使用切片语法将数组或指针转换为切片。

切片具有边界检查,因此可以防止此类非法行为。这是我们更倾向于切片而非指针的一个原因。

test_slice_bounds.zig
const expect = @import("std").testing.expect;

test "pointer slicing" {
    var array = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    var start: usize = 2; // var to make it runtime-known
    _ = &start; // suppress 'var is never mutated' error
    const slice = array[start..4];
    try expect(slice.len == 2);

    try expect(array[3] == 4);
    slice[1] += 1;
    try expect(array[3] == 5);
}
Shell
$ zig test test_slice_bounds.zig
1/1 test_slice_bounds.test.pointer slicing...OK
All 1 tests passed.

指针在编译期也有效,只要代码不依赖于未定义的内存布局

test_comptime_pointers.zig
const expect = @import("std").testing.expect;

test "comptime pointers" {
    comptime {
        var x: i32 = 1;
        const ptr = &x;
        ptr.* += 1;
        x += 1;
        try expect(ptr.* == 3);
    }
}
Shell
$ zig test test_comptime_pointers.zig
1/1 test_comptime_pointers.test.comptime pointers...OK
All 1 tests passed.

要将整数地址转换为指针,请使用 @ptrFromInt。要将指针转换为整数,请使用 @intFromPtr

test_integer_pointer_conversion.zig
const expect = @import("std").testing.expect;

test "@intFromPtr and @ptrFromInt" {
    const ptr: *i32 = @ptrFromInt(0xdeadbee0);
    const addr = @intFromPtr(ptr);
    try expect(@TypeOf(addr) == usize);
    try expect(addr == 0xdeadbee0);
}
Shell
$ zig test test_integer_pointer_conversion.zig
1/1 test_integer_pointer_conversion.test.@intFromPtr and @ptrFromInt...OK
All 1 tests passed.

Zig 能够在编译期代码中保留内存地址,只要指针从不被解引用。

test_comptime_pointer_conversion.zig
const expect = @import("std").testing.expect;

test "comptime @ptrFromInt" {
    comptime {
        // Zig is able to do this at compile-time, as long as
        // ptr is never dereferenced.
        const ptr: *i32 = @ptrFromInt(0xdeadbee0);
        const addr = @intFromPtr(ptr);
        try expect(@TypeOf(addr) == usize);
        try expect(addr == 0xdeadbee0);
    }
}
Shell
$ zig test test_comptime_pointer_conversion.zig
1/1 test_comptime_pointer_conversion.test.comptime @ptrFromInt...OK
All 1 tests passed.

@ptrCast 将指针的元素类型转换为另一种类型。这会创建一个新指针,根据通过它的加载和存储,可能会导致无法检测的非法行为。通常,如果可能,其他类型的类型转换比 @ptrCast 更可取。

test_pointer_casting.zig
const std = @import("std");
const expect = std.testing.expect;

test "pointer casting" {
    const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
    const u32_ptr: *const u32 = @ptrCast(&bytes);
    try expect(u32_ptr.* == 0x12121212);

    // Even this example is contrived - there are better ways to do the above than
    // pointer casting. For example, using a slice narrowing cast:
    const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0];
    try expect(u32_value == 0x12121212);

    // And even another way, the most straightforward way to do it:
    try expect(@as(u32, @bitCast(bytes)) == 0x12121212);
}

test "pointer child type" {
    // pointer types have a `child` field which tells you the type they point to.
    try expect(@typeInfo(*u32).pointer.child == u32);
}
Shell
$ zig test test_pointer_casting.zig
1/2 test_pointer_casting.test.pointer casting...OK
2/2 test_pointer_casting.test.pointer child type...OK
All 2 tests passed.

另请参阅

volatile §

加载和存储被假定没有副作用。如果给定的加载或存储应具有副作用,例如内存映射输入/输出(MMIO),请使用 volatile。在以下代码中,使用 mmio_ptr 的加载和存储保证全部发生,并且与源代码中的顺序相同

test_volatile.zig
const expect = @import("std").testing.expect;

test "volatile" {
    const mmio_ptr: *volatile u8 = @ptrFromInt(0x12345678);
    try expect(@TypeOf(mmio_ptr) == *volatile u8);
}
Shell
$ zig test test_volatile.zig
1/1 test_volatile.test.volatile...OK
All 1 tests passed.

请注意,volatile 与并发和原子操作无关。如果您看到代码将 volatile 用于内存映射输入/输出以外的目的,那很可能是一个错误。

对齐 §

每种类型都有一个对齐值——一个字节数,当该类型的值从内存加载或存储到内存时,内存地址必须能被这个数整除。您可以使用 @alignOf 来查找任何类型的此值。

对齐取决于 CPU 架构,但它始终是 2 的幂,并且小于 1 << 29

在 Zig 中,指针类型有一个对齐值。如果该值等于底层类型的对齐值,则可以从类型中省略。

test_variable_alignment.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;

test "variable alignment" {
    var x: i32 = 1234;
    const align_of_i32 = @alignOf(@TypeOf(x));
    try expect(@TypeOf(&x) == *i32);
    try expect(*i32 == *align(align_of_i32) i32);
    if (builtin.target.cpu.arch == .x86_64) {
        try expect(@typeInfo(*i32).pointer.alignment == 4);
    }
}
Shell
$ zig test test_variable_alignment.zig
1/1 test_variable_alignment.test.variable alignment...OK
All 1 tests passed.

就像 *i32 可以被强制转换为 *const i32 一样,具有更大对齐的指针可以隐式转换为具有更小对齐的指针,反之则不行。

您可以在变量和函数上指定对齐。如果这样做,则指向它们的指针会获得指定的对齐方式。

test_variable_func_alignment.zig
const expect = @import("std").testing.expect;

var foo: u8 align(4) = 100;

test "global variable alignment" {
    try expect(@typeInfo(@TypeOf(&foo)).pointer.alignment == 4);
    try expect(@TypeOf(&foo) == *align(4) u8);
    const as_pointer_to_array: *align(4) [1]u8 = &foo;
    const as_slice: []align(4) u8 = as_pointer_to_array;
    const as_unaligned_slice: []u8 = as_slice;
    try expect(as_unaligned_slice[0] == 100);
}

fn derp() align(@sizeOf(usize) * 2) i32 {
    return 1234;
}
fn noop1() align(1) void {}
fn noop4() align(4) void {}

test "function alignment" {
    try expect(derp() == 1234);
    try expect(@TypeOf(derp) == fn () i32);
    try expect(@TypeOf(&derp) == *align(@sizeOf(usize) * 2) const fn () i32);

    noop1();
    try expect(@TypeOf(noop1) == fn () void);
    try expect(@TypeOf(&noop1) == *align(1) const fn () void);

    noop4();
    try expect(@TypeOf(noop4) == fn () void);
    try expect(@TypeOf(&noop4) == *align(4) const fn () void);
}
Shell
$ zig test test_variable_func_alignment.zig
1/2 test_variable_func_alignment.test.global variable alignment...OK
2/2 test_variable_func_alignment.test.function alignment...OK
All 2 tests passed.

如果您的指针或切片具有较小的对齐方式,但您知道它实际上具有更大的对齐方式,请使用 @alignCast 将指针更改为更对齐的指针。这在运行时是空操作,但会插入一个安全检查

test_incorrect_pointer_alignment.zig
const std = @import("std");

test "pointer alignment safety" {
    var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
    const bytes = std.mem.sliceAsBytes(array[0..]);
    try std.testing.expect(foo(bytes) == 0x11111111);
}
fn foo(bytes: []u8) u32 {
    const slice4 = bytes[1..5];
    const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
    return int_slice[0];
}
Shell
$ zig test test_incorrect_pointer_alignment.zig
1/1 test_incorrect_pointer_alignment.test.pointer alignment safety...thread 198705 panic: incorrect alignment
/home/andy/dev/zig/doc/langref/test_incorrect_pointer_alignment.zig:10:68: 0x1048b82 in foo (test)
    const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                                   ^
/home/andy/dev/zig/doc/langref/test_incorrect_pointer_alignment.zig:6:31: 0x1048a2f in test.pointer alignment safety (test)
    try std.testing.expect(foo(bytes) == 0x11111111);
                              ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef475 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e785d in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e6cd2 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e68ad in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/c74c6fc05a4afb04990e9fe5ae00c4c0/test --seed=0x77765cb4

allowzero §

此指针属性允许指针的地址为零。这仅在独立操作系统目标上需要,其中地址零是可映射的。如果要表示空指针,请改用可选指针。带有 allowzero可选指针与普通指针大小不同。在此代码示例中,如果指针没有 allowzero 属性,则这将是指针类型转换中的无效 null 恐慌。

test_allowzero.zig
const std = @import("std");
const expect = std.testing.expect;

test "allowzero" {
    var zero: usize = 0; // var to make to runtime-known
    _ = &zero; // suppress 'var is never mutated' error
    const ptr: *allowzero i32 = @ptrFromInt(zero);
    try expect(@intFromPtr(ptr) == 0);
}
Shell
$ zig test test_allowzero.zig
1/1 test_allowzero.test.allowzero...OK
All 1 tests passed.

哨兵终止指针 §

语法 [*:x]T 描述了一个其长度由哨兵值确定的指针。这提供了防止缓冲区溢出和超读的保护。

sentinel-terminated_pointer.zig
const std = @import("std");

// This is also available as `std.c.printf`.
pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;

pub fn main() anyerror!void {
    _ = printf("Hello, world!\n"); // OK

    const msg = "Hello, world!\n";
    const non_null_terminated_msg: [msg.len]u8 = msg.*;
    _ = printf(&non_null_terminated_msg);
}
Shell
$ zig build-exe sentinel-terminated_pointer.zig -lc
/home/andy/dev/zig/doc/langref/sentinel-terminated_pointer.zig:11:16: error: expected type '[*:0]const u8', found '*const [14]u8'
    _ = printf(&non_null_terminated_msg);
               ^~~~~~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/sentinel-terminated_pointer.zig:11:16: note: destination pointer requires '0' sentinel
/home/andy/dev/zig/doc/langref/sentinel-terminated_pointer.zig:4:34: note: parameter type declared here
pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
                                 ^~~~~~~~~~~~~
referenced by:
    main: /home/andy/dev/zig/lib/std/start.zig:660:37
    comptime: /home/andy/dev/zig/lib/std/start.zig:58:30
    2 reference(s) hidden; use '-freference-trace=4' to see all references

另请参阅

切片 §

切片是包含指针和长度的结构。数组和切片之间的区别在于,数组的长度是类型的一部分并在编译时已知,而切片的长度在运行时已知。两者都可以通过 len 字段访问。

test_basic_slices.zig
const expect = @import("std").testing.expect;
const expectEqualSlices = @import("std").testing.expectEqualSlices;

test "basic slices" {
    var array = [_]i32{ 1, 2, 3, 4 };
    var known_at_runtime_zero: usize = 0;
    _ = &known_at_runtime_zero;
    const slice = array[known_at_runtime_zero..array.len];

    // alternative initialization using result location
    const alt_slice: []const i32 = &.{ 1, 2, 3, 4 };

    try expectEqualSlices(i32, slice, alt_slice);

    try expect(@TypeOf(slice) == []i32);
    try expect(&slice[0] == &array[0]);
    try expect(slice.len == array.len);

    // If you slice with comptime-known start and end positions, the result is
    // a pointer to an array, rather than a slice.
    const array_ptr = array[0..array.len];
    try expect(@TypeOf(array_ptr) == *[array.len]i32);

    // You can perform a slice-by-length by slicing twice. This allows the compiler
    // to perform some optimisations like recognising a comptime-known length when
    // the start position is only known at runtime.
    var runtime_start: usize = 1;
    _ = &runtime_start;
    const length = 2;
    const array_ptr_len = array[runtime_start..][0..length];
    try expect(@TypeOf(array_ptr_len) == *[length]i32);

    // Using the address-of operator on a slice gives a single-item pointer.
    try expect(@TypeOf(&slice[0]) == *i32);
    // Using the `ptr` field gives a many-item pointer.
    try expect(@TypeOf(slice.ptr) == [*]i32);
    try expect(@intFromPtr(slice.ptr) == @intFromPtr(&slice[0]));

    // Slices have array bounds checking. If you try to access something out
    // of bounds, you'll get a safety check failure:
    slice[10] += 1;

    // Note that `slice.ptr` does not invoke safety checking, while `&slice[0]`
    // asserts that the slice has len > 0.

    // Empty slices can be created like this:
    const empty1 = &[0]u8{};
    // If the type is known you can use this short hand:
    const empty2: []u8 = &.{};
    try expect(empty1.len == 0);
    try expect(empty2.len == 0);

    // A zero-length initialization can always be used to create an empty slice, even if the slice is mutable.
    // This is because the pointed-to data is zero bits long, so its immutability is irrelevant.
}
Shell
$ zig test test_basic_slices.zig
1/1 test_basic_slices.test.basic slices...thread 200412 panic: index out of bounds: index 10, len 4
/home/andy/dev/zig/doc/langref/test_basic_slices.zig:41:10: 0x104b511 in test.basic slices (test)
    slice[10] += 1;
         ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10f2925 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10ead0d in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10ea182 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e9d5d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/59dc8382d8140cff64d1ea721c83a213/test --seed=0x2837bf4f

这是我们更倾向于切片而非指针的一个原因。

test_slices.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;
const fmt = std.fmt;

test "using slices for strings" {
    // Zig has no concept of strings. String literals are const pointers
    // to null-terminated arrays of u8, and by convention parameters
    // that are "strings" are expected to be UTF-8 encoded slices of u8.
    // Here we coerce *const [5:0]u8 and *const [6:0]u8 to []const u8
    const hello: []const u8 = "hello";
    const world: []const u8 = "世界";

    var all_together: [100]u8 = undefined;
    // You can use slice syntax with at least one runtime-known index on an
    // array to convert an array into a slice.
    var start: usize = 0;
    _ = &start;
    const all_together_slice = all_together[start..];
    // String concatenation example.
    const hello_world = try fmt.bufPrint(all_together_slice, "{s} {s}", .{ hello, world });

    // Generally, you can use UTF-8 and not worry about whether something is a
    // string. If you don't need to deal with individual characters, no need
    // to decode.
    try expect(mem.eql(u8, hello_world, "hello 世界"));
}

test "slice pointer" {
    var array: [10]u8 = undefined;
    const ptr = &array;
    try expect(@TypeOf(ptr) == *[10]u8);

    // A pointer to an array can be sliced just like an array:
    var start: usize = 0;
    var end: usize = 5;
    _ = .{ &start, &end };
    const slice = ptr[start..end];
    // The slice is mutable because we sliced a mutable pointer.
    try expect(@TypeOf(slice) == []u8);
    slice[2] = 3;
    try expect(array[2] == 3);

    // Again, slicing with comptime-known indexes will produce another pointer
    // to an array:
    const ptr2 = slice[2..3];
    try expect(ptr2.len == 1);
    try expect(ptr2[0] == 3);
    try expect(@TypeOf(ptr2) == *[1]u8);
}
Shell
$ zig test test_slices.zig
1/2 test_slices.test.using slices for strings...OK
2/2 test_slices.test.slice pointer...OK
All 2 tests passed.

另请参阅

哨兵终止切片 §

语法 [:x]T 是一个切片,它具有运行时已知的长度,并且保证在由长度索引的元素处有一个哨兵值。该类型不保证在此之前没有哨兵元素。哨兵终止切片允许对 len 索引进行元素访问。

test_null_terminated_slice.zig
const std = @import("std");
const expect = std.testing.expect;

test "0-terminated slice" {
    const slice: [:0]const u8 = "hello";

    try expect(slice.len == 5);
    try expect(slice[5] == 0);
}
Shell
$ zig test test_null_terminated_slice.zig
1/1 test_null_terminated_slice.test.0-terminated slice...OK
All 1 tests passed.

哨兵终止切片也可以使用切片语法的变体 data[start..end :x] 来创建,其中 data 是一个多项指针、数组或切片,x 是哨兵值。

test_null_terminated_slicing.zig
const std = @import("std");
const expect = std.testing.expect;

test "0-terminated slicing" {
    var array = [_]u8{ 3, 2, 1, 0, 3, 2, 1, 0 };
    var runtime_length: usize = 3;
    _ = &runtime_length;
    const slice = array[0..runtime_length :0];

    try expect(@TypeOf(slice) == [:0]u8);
    try expect(slice.len == 3);
}
Shell
$ zig test test_null_terminated_slicing.zig
1/1 test_null_terminated_slicing.test.0-terminated slicing...OK
All 1 tests passed.

哨兵终止切片断言支持数据中哨兵位置的元素实际上就是哨兵值。如果不是这种情况,则会导致安全检查的非法行为

test_sentinel_mismatch.zig
const std = @import("std");
const expect = std.testing.expect;

test "sentinel mismatch" {
    var array = [_]u8{ 3, 2, 1, 0 };

    // Creating a sentinel-terminated slice from the array with a length of 2
    // will result in the value `1` occupying the sentinel element position.
    // This does not match the indicated sentinel value of `0` and will lead
    // to a runtime panic.
    var runtime_length: usize = 2;
    _ = &runtime_length;
    const slice = array[0..runtime_length :0];

    _ = slice;
}
Shell
$ zig test test_sentinel_mismatch.zig
1/1 test_sentinel_mismatch.test.sentinel mismatch...thread 201174 panic: sentinel mismatch: expected 0, found 1
/home/andy/dev/zig/doc/langref/test_sentinel_mismatch.zig:13:24: 0x1048a71 in test.sentinel mismatch (test)
    const slice = array[0..runtime_length :0];
                       ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef1d5 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e75bd in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e6a32 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e660d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/3ae1c7f3e7c9fd9d03520f85869dba54/test --seed=0xcab5d72d

另请参阅

struct §

test_structs.zig
// Declare a struct.
// Zig gives no guarantees about the order of fields and the size of
// the struct but the fields are guaranteed to be ABI-aligned.
const Point = struct {
    x: f32,
    y: f32,
};

// Declare an instance of a struct.
const p: Point = .{
    .x = 0.12,
    .y = 0.34,
};

// Functions in the struct's namespace can be called with dot syntax.
const Vec3 = struct {
    x: f32,
    y: f32,
    z: f32,

    pub fn init(x: f32, y: f32, z: f32) Vec3 {
        return Vec3{
            .x = x,
            .y = y,
            .z = z,
        };
    }

    pub fn dot(self: Vec3, other: Vec3) f32 {
        return self.x * other.x + self.y * other.y + self.z * other.z;
    }
};

test "dot product" {
    const v1 = Vec3.init(1.0, 0.0, 0.0);
    const v2 = Vec3.init(0.0, 1.0, 0.0);
    try expect(v1.dot(v2) == 0.0);

    // Other than being available to call with dot syntax, struct methods are
    // not special. You can reference them as any other declaration inside
    // the struct:
    try expect(Vec3.dot(v1, v2) == 0.0);
}

// Structs can have declarations.
// Structs can have 0 fields.
const Empty = struct {
    pub const PI = 3.14;
};
test "struct namespaced variable" {
    try expect(Empty.PI == 3.14);
    try expect(@sizeOf(Empty) == 0);

    // Empty structs can be instantiated the same as usual.
    const does_nothing: Empty = .{};

    _ = does_nothing;
}

// Struct field order is determined by the compiler, however, a base pointer
// can be computed from a field pointer:
fn setYBasedOnX(x: *f32, y: f32) void {
    const point: *Point = @fieldParentPtr("x", x);
    point.y = y;
}
test "field parent pointer" {
    var point = Point{
        .x = 0.1234,
        .y = 0.5678,
    };
    setYBasedOnX(&point.x, 0.9);
    try expect(point.y == 0.9);
}

// Structs can be returned from functions.
fn LinkedList(comptime T: type) type {
    return struct {
        pub const Node = struct {
            prev: ?*Node,
            next: ?*Node,
            data: T,
        };

        first: ?*Node,
        last: ?*Node,
        len: usize,
    };
}

test "linked list" {
    // Functions called at compile-time are memoized.
    try expect(LinkedList(i32) == LinkedList(i32));

    const list = LinkedList(i32){
        .first = null,
        .last = null,
        .len = 0,
    };
    try expect(list.len == 0);

    // Since types are first class values you can instantiate the type
    // by assigning it to a variable:
    const ListOfInts = LinkedList(i32);
    try expect(ListOfInts == LinkedList(i32));

    var node = ListOfInts.Node{
        .prev = null,
        .next = null,
        .data = 1234,
    };
    const list2 = LinkedList(i32){
        .first = &node,
        .last = &node,
        .len = 1,
    };

    // When using a pointer to a struct, fields can be accessed directly,
    // without explicitly dereferencing the pointer.
    // So you can do
    try expect(list2.first.?.data == 1234);
    // instead of try expect(list2.first.?.*.data == 1234);
}

const expect = @import("std").testing.expect;
Shell
$ zig test test_structs.zig
1/4 test_structs.test.dot product...OK
2/4 test_structs.test.struct namespaced variable...OK
3/4 test_structs.test.field parent pointer...OK
4/4 test_structs.test.linked list...OK
All 4 tests passed.

默认字段值 §

每个结构体字段都可以有一个表达式来指示默认字段值。此类表达式在编译期执行,并允许在结构体字面量表达式中省略该字段

struct_default_field_values.zig
const Foo = struct {
    a: i32 = 1234,
    b: i32,
};

test "default struct initialization fields" {
    const x: Foo = .{
        .b = 5,
    };
    if (x.a + x.b != 1239) {
        comptime unreachable;
    }
}
Shell
$ zig test struct_default_field_values.zig
1/1 struct_default_field_values.test.default struct initialization fields...OK
All 1 tests passed.

有缺陷的默认字段值 §

默认字段值仅适用于通过在初始化中省略该字段不会违反结构体数据不变量的情况。

例如,以下是默认结构体字段初始化不恰当的用法:

bad_default_value.zig
const Threshold = struct {
    minimum: f32 = 0.25,
    maximum: f32 = 0.75,

    const Category = enum { low, medium, high };

    fn categorize(t: Threshold, value: f32) Category {
        assert(t.maximum >= t.minimum);
        if (value < t.minimum) return .low;
        if (value > t.maximum) return .high;
        return .medium;
    }
};

pub fn main() !void {
    var threshold: Threshold = .{
        .maximum = 0.20,
    };
    const category = threshold.categorize(0.90);
    try std.io.getStdOut().writeAll(@tagName(category));
}

const std = @import("std");
const assert = std.debug.assert;
Shell
$ zig build-exe bad_default_value.zig
$ ./bad_default_value
thread 206555 panic: reached unreachable code
/home/andy/dev/zig/lib/std/debug.zig:550:14: 0x1048dbd in assert (bad_default_value)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/dev/zig/doc/langref/bad_default_value.zig:8:15: 0x10de7d9 in categorize (bad_default_value)
        assert(t.maximum >= t.minimum);
              ^
/home/andy/dev/zig/doc/langref/bad_default_value.zig:19:42: 0x10de71a in main (bad_default_value)
    const category = threshold.categorize(0.90);
                                         ^
/home/andy/dev/zig/lib/std/start.zig:660:37: 0x10de62a in posixCallMainAndExit (bad_default_value)
            const result = root.main() catch |err| {
                                    ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10de1dd in _start (bad_default_value)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

从上文可以看出忽略此原则的危险。默认字段值导致数据不变量被破坏,从而引发非法行为。

要解决此问题,请删除所有结构体字段的默认值,并提供一个命名默认值:

struct_default_value.zig
const Threshold = struct {
    minimum: f32,
    maximum: f32,

    const default: Threshold = .{
        .minimum = 0.25,
        .maximum = 0.75,
    };
};

如果结构体值需要一个运行时已知的值才能在不违反数据不变量的情况下进行初始化,那么请使用接受这些运行时值并填充其余字段的初始化方法。

extern struct §

extern struct 的内存布局与目标 C ABI 匹配。

如果不需要明确定义的内存布局,struct 是更好的选择,因为它对编译器施加的限制更少。

有关具有其支持整数 ABI 的结构体,请参阅packed struct,这对于建模标志很有用。

另请参阅

packed struct §

与普通结构体不同,packed 结构体保证内存布局:

  • 字段保持声明顺序,从最低有效位到最高有效位。
  • 字段之间没有填充。
  • Zig 支持任意位宽的整数,虽然通常情况下,小于 8 位的整数仍然会使用 1 字节内存,但在 packed struct 中,它们会精确使用其位宽。
  • bool 字段精确使用 1 位。
  • 一个枚举字段精确使用其整数标签类型的位宽。
  • 一个packed union 字段精确使用联合体中位宽最大的字段的位宽。
  • Packed struct 支持相等运算符。

这意味着 packed struct 可以参与 @bitCast@ptrCast 来重新解释内存。这甚至在编译期也有效。

test_packed_structs.zig
const std = @import("std");
const native_endian = @import("builtin").target.cpu.arch.endian();
const expect = std.testing.expect;

const Full = packed struct {
    number: u16,
};
const Divided = packed struct {
    half1: u8,
    quarter3: u4,
    quarter4: u4,
};

test "@bitCast between packed structs" {
    try doTheTest();
    try comptime doTheTest();
}

fn doTheTest() !void {
    try expect(@sizeOf(Full) == 2);
    try expect(@sizeOf(Divided) == 2);
    const full = Full{ .number = 0x1234 };
    const divided: Divided = @bitCast(full);
    try expect(divided.half1 == 0x34);
    try expect(divided.quarter3 == 0x2);
    try expect(divided.quarter4 == 0x1);

    const ordered: [2]u8 = @bitCast(full);
    switch (native_endian) {
        .big => {
            try expect(ordered[0] == 0x12);
            try expect(ordered[1] == 0x34);
        },
        .little => {
            try expect(ordered[0] == 0x34);
            try expect(ordered[1] == 0x12);
        },
    }
}
Shell
$ zig test test_packed_structs.zig
1/1 test_packed_structs.test.@bitCast between packed structs...OK
All 1 tests passed.

支持整数是根据字段的总位宽推断出来的。可以选择性地显式提供并在编译时强制执行。

test_missized_packed_struct.zig
test "missized packed struct" {
    const S = packed struct(u32) { a: u16, b: u8 };
    _ = S{ .a = 4, .b = 2 };
}
Shell
$ zig test test_missized_packed_struct.zig
/home/andy/dev/zig/doc/langref/test_missized_packed_struct.zig:2:29: error: backing integer type 'u32' has bit size 32 but the struct fields have a total bit size of 24
    const S = packed struct(u32) { a: u16, b: u8 };
                            ^~~
referenced by:
    test.missized packed struct: /home/andy/dev/zig/doc/langref/test_missized_packed_struct.zig:2:22

Zig 允许获取非字节对齐字段的地址。

test_pointer_to_non-byte_aligned_field.zig
const std = @import("std");
const expect = std.testing.expect;

const BitField = packed struct {
    a: u3,
    b: u3,
    c: u2,
};

var foo = BitField{
    .a = 1,
    .b = 2,
    .c = 3,
};

test "pointer to non-byte-aligned field" {
    const ptr = &foo.b;
    try expect(ptr.* == 2);
}
Shell
$ zig test test_pointer_to_non-byte_aligned_field.zig
1/1 test_pointer_to_non-byte_aligned_field.test.pointer to non-byte-aligned field...OK
All 1 tests passed.

然而,指向非字节对齐字段的指针具有特殊属性,不能在预期普通指针时传递。

test_misaligned_pointer.zig
const std = @import("std");
const expect = std.testing.expect;

const BitField = packed struct {
    a: u3,
    b: u3,
    c: u2,
};

var bit_field = BitField{
    .a = 1,
    .b = 2,
    .c = 3,
};

test "pointer to non-byte-aligned field" {
    try expect(bar(&bit_field.b) == 2);
}

fn bar(x: *const u3) u3 {
    return x.*;
}
Shell
$ zig test test_misaligned_pointer.zig
/home/andy/dev/zig/doc/langref/test_misaligned_pointer.zig:17:20: error: expected type '*const u3', found '*align(1:3:1) u3'
    try expect(bar(&bit_field.b) == 2);
                   ^~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_misaligned_pointer.zig:17:20: note: pointer host size '1' cannot cast into pointer host size '0'
/home/andy/dev/zig/doc/langref/test_misaligned_pointer.zig:17:20: note: pointer bit offset '3' cannot cast into pointer bit offset '0'
/home/andy/dev/zig/doc/langref/test_misaligned_pointer.zig:20:11: note: parameter type declared here
fn bar(x: *const u3) u3 {
          ^~~~~~~~~

在这种情况下,函数 bar 无法被调用,因为指向非 ABI 对齐字段的指针提到了位偏移,但函数期望的是 ABI 对齐的指针。

指向非 ABI 对齐字段的指针与宿主整数内的其他字段共享相同的地址。

test_packed_struct_field_address.zig
const std = @import("std");
const expect = std.testing.expect;

const BitField = packed struct {
    a: u3,
    b: u3,
    c: u2,
};

var bit_field = BitField{
    .a = 1,
    .b = 2,
    .c = 3,
};

test "pointers of sub-byte-aligned fields share addresses" {
    try expect(@intFromPtr(&bit_field.a) == @intFromPtr(&bit_field.b));
    try expect(@intFromPtr(&bit_field.a) == @intFromPtr(&bit_field.c));
}
Shell
$ zig test test_packed_struct_field_address.zig
1/1 test_packed_struct_field_address.test.pointers of sub-byte-aligned fields share addresses...OK
All 1 tests passed.

这可以通过 @bitOffsetOfoffsetOf 观察到。

test_bitOffsetOf_offsetOf.zig
const std = @import("std");
const expect = std.testing.expect;

const BitField = packed struct {
    a: u3,
    b: u3,
    c: u2,
};

test "offsets of non-byte-aligned fields" {
    comptime {
        try expect(@bitOffsetOf(BitField, "a") == 0);
        try expect(@bitOffsetOf(BitField, "b") == 3);
        try expect(@bitOffsetOf(BitField, "c") == 6);

        try expect(@offsetOf(BitField, "a") == 0);
        try expect(@offsetOf(BitField, "b") == 0);
        try expect(@offsetOf(BitField, "c") == 0);
    }
}
Shell
$ zig test test_bitOffsetOf_offsetOf.zig
1/1 test_bitOffsetOf_offsetOf.test.offsets of non-byte-aligned fields...OK
All 1 tests passed.

packed struct 具有与其支持整数相同的对齐方式,但是,过度对齐的 packed struct 指针可以覆盖此对齐方式。

test_overaligned_packed_struct.zig
const std = @import("std");
const expect = std.testing.expect;

const S = packed struct {
    a: u32,
    b: u32,
};
test "overaligned pointer to packed struct" {
    var foo: S align(4) = .{ .a = 1, .b = 2 };
    const ptr: *align(4) S = &foo;
    const ptr_to_b: *u32 = &ptr.b;
    try expect(ptr_to_b.* == 2);
}
Shell
$ zig test test_overaligned_packed_struct.zig
1/1 test_overaligned_packed_struct.test.overaligned pointer to packed struct...OK
All 1 tests passed.

也可以设置结构体字段的对齐方式。

test_aligned_struct_fields.zig
const std = @import("std");
const expectEqual = std.testing.expectEqual;

test "aligned struct fields" {
    const S = struct {
        a: u32 align(2),
        b: u32 align(64),
    };
    var foo = S{ .a = 1, .b = 2 };

    try expectEqual(64, @alignOf(S));
    try expectEqual(*align(2) u32, @TypeOf(&foo.a));
    try expectEqual(*align(64) u32, @TypeOf(&foo.b));
}
Shell
$ zig test test_aligned_struct_fields.zig
1/1 test_aligned_struct_fields.test.aligned struct fields...OK
All 1 tests passed.

对 packed struct 进行相等比较会比较其支持整数,并且仅适用于 `==` 和 `!=` 运算符。

test_packed_struct_equality.zig
const std = @import("std");
const expect = std.testing.expect;

test "packed struct equality" {
    const S = packed struct {
        a: u4,
        b: u4,
    };
    const x: S = .{ .a = 1, .b = 2 };
    const y: S = .{ .b = 2, .a = 1 };
    try expect(x == y);
}
Shell
$ zig test test_packed_struct_equality.zig
1/1 test_packed_struct_equality.test.packed struct equality...OK
All 1 tests passed.

将 packed struct 与 volatile 一起使用是有问题的,未来可能会导致编译错误。有关详细信息,请订阅此问题。TODO 一旦此问题解决,请更新这些文档,说明如何将 packed struct 与 MMIO(volatile packed struct 的用例)一起使用。别担心,Zig 将为此用例提供一个很好的解决方案。

结构体命名 §

由于所有结构体都是匿名的,Zig 会根据一些规则推断其类型名称。

  • 如果结构体位于变量的初始化表达式中,它将以该变量命名。
  • 如果结构体位于 return 表达式中,它将以其返回的函数命名,并序列化参数值。
  • 否则,结构体将获得一个名称,例如 (filename.funcname__struct_ID)
  • 如果结构体声明在另一个结构体内部,它将以父结构体的名称和根据上述规则推断出的名称命名,并用点分隔。
struct_name.zig
const std = @import("std");

pub fn main() void {
    const Foo = struct {};
    std.debug.print("variable: {s}\n", .{@typeName(Foo)});
    std.debug.print("anonymous: {s}\n", .{@typeName(struct {})});
    std.debug.print("function: {s}\n", .{@typeName(List(i32))});
}

fn List(comptime T: type) type {
    return struct {
        x: T,
    };
}
Shell
$ zig build-exe struct_name.zig
$ ./struct_name
variable: struct_name.main.Foo
anonymous: struct_name.main__struct_24147
function: struct_name.List(i32)

匿名结构体字面量 §

Zig 允许省略字面量的结构体类型。当结果被强制转换时,结构体字面量将直接实例化结果位置,不进行复制。

test_struct_result.zig
const std = @import("std");
const expect = std.testing.expect;

const Point = struct { x: i32, y: i32 };

test "anonymous struct literal" {
    const pt: Point = .{
        .x = 13,
        .y = 67,
    };
    try expect(pt.x == 13);
    try expect(pt.y == 67);
}
Shell
$ zig test test_struct_result.zig
1/1 test_struct_result.test.anonymous struct literal...OK
All 1 tests passed.

结构体类型可以被推断。此处结果位置不包含类型,因此 Zig 会推断类型。

test_anonymous_struct.zig
const std = @import("std");
const expect = std.testing.expect;

test "fully anonymous struct" {
    try check(.{
        .int = @as(u32, 1234),
        .float = @as(f64, 12.34),
        .b = true,
        .s = "hi",
    });
}

fn check(args: anytype) !void {
    try expect(args.int == 1234);
    try expect(args.float == 12.34);
    try expect(args.b);
    try expect(args.s[0] == 'h');
    try expect(args.s[1] == 'i');
}
Shell
$ zig test test_anonymous_struct.zig
1/1 test_anonymous_struct.test.fully anonymous struct...OK
All 1 tests passed.

元组 (Tuples) §

匿名结构体可以在不指定字段名称的情况下创建,并被称为“元组”。一个空元组看起来像 .{},可以在一个Hello World 示例中看到。

字段隐式地使用从 0 开始的数字命名。由于它们的名称是整数,如果不将它们包裹在 @"" 中,则无法使用 . 语法访问它们。@"" 内部的名称始终被识别为标识符

与数组类似,元组具有 .len 字段,可以被索引(前提是索引在编译期已知),并与 ++ 和 ** 运算符一起使用。它们也可以通过内联 for 循环进行迭代。

test_tuples.zig
const std = @import("std");
const expect = std.testing.expect;

test "tuple" {
    const values = .{
        @as(u32, 1234),
        @as(f64, 12.34),
        true,
        "hi",
    } ++ .{false} ** 2;
    try expect(values[0] == 1234);
    try expect(values[4] == false);
    inline for (values, 0..) |v, i| {
        if (i != 2) continue;
        try expect(v);
    }
    try expect(values.len == 6);
    try expect(values.@"3"[0] == 'h');
}
Shell
$ zig test test_tuples.zig
1/1 test_tuples.test.tuple...OK
All 1 tests passed.

元组解构 §

元组可以被解构

元组解构有助于从代码块返回多个值。

destructuring_block.zig
const print = @import("std").debug.print;

pub fn main() void {
    const digits = [_]i8 { 3, 8, 9, 0, 7, 4, 1 };

    const min, const max = blk: {
        var min: i8 = 127;
        var max: i8 = -128;

        for (digits) |digit| {
            if (digit < min) min = digit;
            if (digit > max) max = digit;
        }

        break :blk .{ min, max };
    };

    print("min = {}", .{ min });
    print("max = {}", .{ max });
}
Shell
$ zig build-exe destructuring_block.zig
$ ./destructuring_block
min = 0max = 9

元组解构有助于处理以元组形式返回多个值的函数和内置函数。

destructuring_return_value.zig
const print = @import("std").debug.print;

fn divmod(numerator: u32, denominator: u32) struct { u32, u32 } {
    return .{ numerator / denominator, numerator % denominator };
}

pub fn main() void {
    const div, const mod = divmod(10, 3);

    print("10 / 3 = {}\n", .{div});
    print("10 % 3 = {}\n", .{mod});
}
Shell
$ zig build-exe destructuring_return_value.zig
$ ./destructuring_return_value
10 / 3 = 3
10 % 3 = 1

另请参阅

另请参阅

enum §

test_enums.zig
const expect = @import("std").testing.expect;
const mem = @import("std").mem;

// Declare an enum.
const Type = enum {
    ok,
    not_ok,
};

// Declare a specific enum field.
const c = Type.ok;

// If you want access to the ordinal value of an enum, you
// can specify the tag type.
const Value = enum(u2) {
    zero,
    one,
    two,
};
// Now you can cast between u2 and Value.
// The ordinal value starts from 0, counting up by 1 from the previous member.
test "enum ordinal value" {
    try expect(@intFromEnum(Value.zero) == 0);
    try expect(@intFromEnum(Value.one) == 1);
    try expect(@intFromEnum(Value.two) == 2);
}

// You can override the ordinal value for an enum.
const Value2 = enum(u32) {
    hundred = 100,
    thousand = 1000,
    million = 1000000,
};
test "set enum ordinal value" {
    try expect(@intFromEnum(Value2.hundred) == 100);
    try expect(@intFromEnum(Value2.thousand) == 1000);
    try expect(@intFromEnum(Value2.million) == 1000000);
}

// You can also override only some values.
const Value3 = enum(u4) {
    a,
    b = 8,
    c,
    d = 4,
    e,
};
test "enum implicit ordinal values and overridden values" {
    try expect(@intFromEnum(Value3.a) == 0);
    try expect(@intFromEnum(Value3.b) == 8);
    try expect(@intFromEnum(Value3.c) == 9);
    try expect(@intFromEnum(Value3.d) == 4);
    try expect(@intFromEnum(Value3.e) == 5);
}

// Enums can have methods, the same as structs and unions.
// Enum methods are not special, they are only namespaced
// functions that you can call with dot syntax.
const Suit = enum {
    clubs,
    spades,
    diamonds,
    hearts,

    pub fn isClubs(self: Suit) bool {
        return self == Suit.clubs;
    }
};
test "enum method" {
    const p = Suit.spades;
    try expect(!p.isClubs());
}

// An enum can be switched upon.
const Foo = enum {
    string,
    number,
    none,
};
test "enum switch" {
    const p = Foo.number;
    const what_is_it = switch (p) {
        Foo.string => "this is a string",
        Foo.number => "this is a number",
        Foo.none => "this is a none",
    };
    try expect(mem.eql(u8, what_is_it, "this is a number"));
}

// @typeInfo can be used to access the integer tag type of an enum.
const Small = enum {
    one,
    two,
    three,
    four,
};
test "std.meta.Tag" {
    try expect(@typeInfo(Small).@"enum".tag_type == u2);
}

// @typeInfo tells us the field count and the fields names:
test "@typeInfo" {
    try expect(@typeInfo(Small).@"enum".fields.len == 4);
    try expect(mem.eql(u8, @typeInfo(Small).@"enum".fields[1].name, "two"));
}

// @tagName gives a [:0]const u8 representation of an enum value:
test "@tagName" {
    try expect(mem.eql(u8, @tagName(Small.three), "three"));
}
Shell
$ zig test test_enums.zig
1/8 test_enums.test.enum ordinal value...OK
2/8 test_enums.test.set enum ordinal value...OK
3/8 test_enums.test.enum implicit ordinal values and overridden values...OK
4/8 test_enums.test.enum method...OK
5/8 test_enums.test.enum switch...OK
6/8 test_enums.test.std.meta.Tag...OK
7/8 test_enums.test.@typeInfo...OK
8/8 test_enums.test.@tagName...OK
All 8 tests passed.

另请参阅

extern enum §

默认情况下,枚举不保证与 C ABI 兼容。

enum_export_error.zig
const Foo = enum { a, b, c };
export fn entry(foo: Foo) void {
    _ = foo;
}
Shell
$ zig build-obj enum_export_error.zig -target x86_64-linux
/home/andy/dev/zig/doc/langref/enum_export_error.zig:2:17: error: parameter of type 'enum_export_error.Foo' not allowed in function with calling convention 'x86_64_sysv'
export fn entry(foo: Foo) void {
                ^~~~~~~~
/home/andy/dev/zig/doc/langref/enum_export_error.zig:2:17: note: enum tag type 'u2' is not extern compatible
/home/andy/dev/zig/doc/langref/enum_export_error.zig:2:17: note: only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible
/home/andy/dev/zig/doc/langref/enum_export_error.zig:1:13: note: enum declared here
const Foo = enum { a, b, c };
            ^~~~~~~~~~~~~~~~
referenced by:
    root: /home/andy/dev/zig/lib/std/start.zig:3:22
    comptime: /home/andy/dev/zig/lib/std/start.zig:27:9
    2 reference(s) hidden; use '-freference-trace=4' to see all references

对于 C ABI 兼容的枚举,请为枚举提供显式标签类型。

enum_export.zig
const Foo = enum(c_int) { a, b, c };
export fn entry(foo: Foo) void {
    _ = foo;
}
Shell
$ zig build-obj enum_export.zig

枚举字面量 §

枚举字面量允许指定枚举字段的名称而无需指定枚举类型。

test_enum_literals.zig
const std = @import("std");
const expect = std.testing.expect;

const Color = enum {
    auto,
    off,
    on,
};

test "enum literals" {
    const color1: Color = .auto;
    const color2 = Color.auto;
    try expect(color1 == color2);
}

test "switch using enum literals" {
    const color = Color.on;
    const result = switch (color) {
        .auto => false,
        .on => true,
        .off => false,
    };
    try expect(result);
}
Shell
$ zig test test_enum_literals.zig
1/2 test_enum_literals.test.enum literals...OK
2/2 test_enum_literals.test.switch using enum literals...OK
All 2 tests passed.

非穷尽枚举 §

可以通过添加一个尾随的 _ 字段来创建非穷尽枚举。枚举必须指定一个标签类型,并且不能耗尽每个枚举值。

在非穷尽枚举上使用 @enumFromInt 涉及将 @intCast 到整数标签类型的安全语义,但除此之外,它总是会产生一个定义良好的枚举值。

非穷尽枚举上的 switch 语句可以包含一个 _ 分支作为 else 分支的替代。使用 _ 分支时,如果 switch 没有处理所有已知的标签名称,编译器会报错。

test_switch_non-exhaustive.zig
const std = @import("std");
const expect = std.testing.expect;

const Number = enum(u8) {
    one,
    two,
    three,
    _,
};

test "switch on non-exhaustive enum" {
    const number = Number.one;
    const result = switch (number) {
        .one => true,
        .two, .three => false,
        _ => false,
    };
    try expect(result);
    const is_one = switch (number) {
        .one => true,
        else => false,
    };
    try expect(is_one);
}
Shell
$ zig test test_switch_non-exhaustive.zig
1/1 test_switch_non-exhaustive.test.switch on non-exhaustive enum...OK
All 1 tests passed.

union §

一个裸 union 将一个值可以拥有的可能类型定义为字段列表。一次只能有一个字段处于活动状态。裸联合体的内存表示不作保证。裸联合体不能用于重新解释内存。为此,请使用 @ptrCast,或使用具有保证内存布局的 extern unionpacked union访问非活动字段是经过安全检查的非法行为

test_wrong_union_access.zig
const Payload = union {
    int: i64,
    float: f64,
    boolean: bool,
};
test "simple union" {
    var payload = Payload{ .int = 1234 };
    payload.float = 12.34;
}
Shell
$ zig test test_wrong_union_access.zig
1/1 test_wrong_union_access.test.simple union...thread 210915 panic: access of union field 'float' while field 'int' is active
/home/andy/dev/zig/doc/langref/test_wrong_union_access.zig:8:12: 0x1048a5f in test.simple union (test)
    payload.float = 12.34;
           ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef2e5 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e76cd in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e6b42 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e671d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/0e2ad1837ee3fe26786cd418343cba9e/test --seed=0xdea5f103

您可以通过赋值整个联合体来激活另一个字段。

test_simple_union.zig
const std = @import("std");
const expect = std.testing.expect;

const Payload = union {
    int: i64,
    float: f64,
    boolean: bool,
};
test "simple union" {
    var payload = Payload{ .int = 1234 };
    try expect(payload.int == 1234);
    payload = Payload{ .float = 12.34 };
    try expect(payload.float == 12.34);
}
Shell
$ zig test test_simple_union.zig
1/1 test_simple_union.test.simple union...OK
All 1 tests passed.

为了在联合体中使用 switch,它必须是一个带标签联合体

要在标签为编译期已知名称时初始化联合体,请参阅 @unionInit

带标签联合体 (Tagged union) §

联合体可以使用枚举标签类型来声明。这会将联合体转换为一个带标签的联合体,使其能够与switch表达式一起使用。带标签的联合体可以强制转换为它们的标签类型:类型强制转换:联合体和枚举

test_tagged_union.zig
const std = @import("std");
const expect = std.testing.expect;

const ComplexTypeTag = enum {
    ok,
    not_ok,
};
const ComplexType = union(ComplexTypeTag) {
    ok: u8,
    not_ok: void,
};

test "switch on tagged union" {
    const c = ComplexType{ .ok = 42 };
    try expect(@as(ComplexTypeTag, c) == ComplexTypeTag.ok);

    switch (c) {
        .ok => |value| try expect(value == 42),
        .not_ok => unreachable,
    }
}

test "get tag type" {
    try expect(std.meta.Tag(ComplexType) == ComplexTypeTag);
}
Shell
$ zig test test_tagged_union.zig
1/2 test_tagged_union.test.switch on tagged union...OK
2/2 test_tagged_union.test.get tag type...OK
All 2 tests passed.

为了在switch表达式中修改带标签联合体的有效载荷,在变量名前面加上一个*,使其成为指针。

test_switch_modify_tagged_union.zig
const std = @import("std");
const expect = std.testing.expect;

const ComplexTypeTag = enum {
    ok,
    not_ok,
};
const ComplexType = union(ComplexTypeTag) {
    ok: u8,
    not_ok: void,
};

test "modify tagged union in switch" {
    var c = ComplexType{ .ok = 42 };

    switch (c) {
        ComplexTypeTag.ok => |*value| value.* += 1,
        ComplexTypeTag.not_ok => unreachable,
    }

    try expect(c.ok == 43);
}
Shell
$ zig test test_switch_modify_tagged_union.zig
1/1 test_switch_modify_tagged_union.test.modify tagged union in switch...OK
All 1 tests passed.

联合体可以推断枚举标签类型。此外,联合体和结构体、枚举一样可以拥有方法。

test_union_method.zig
const std = @import("std");
const expect = std.testing.expect;

const Variant = union(enum) {
    int: i32,
    boolean: bool,

    // void can be omitted when inferring enum tag type.
    none,

    fn truthy(self: Variant) bool {
        return switch (self) {
            Variant.int => |x_int| x_int != 0,
            Variant.boolean => |x_bool| x_bool,
            Variant.none => false,
        };
    }
};

test "union method" {
    var v1: Variant = .{ .int = 1 };
    var v2: Variant = .{ .boolean = false };
    var v3: Variant = .none;

    try expect(v1.truthy());
    try expect(!v2.truthy());
    try expect(!v3.truthy());
}
Shell
$ zig test test_union_method.zig
1/1 test_union_method.test.union method...OK
All 1 tests passed.

@tagName可用于返回一个表示字段名称的编译期[:0]const u8值。

test_tagName.zig
const std = @import("std");
const expect = std.testing.expect;

const Small2 = union(enum) {
    a: i32,
    b: bool,
    c: u8,
};
test "@tagName" {
    try expect(std.mem.eql(u8, @tagName(Small2.a), "a"));
}
Shell
$ zig test test_tagName.zig
1/1 test_tagName.test.@tagName...OK
All 1 tests passed.

extern union §

extern union 的内存布局保证与目标 C ABI 兼容。

另请参阅

packed union §

packed union 具有明确定义的内存布局,并有资格包含在packed struct中。

匿名联合体字面量 §

匿名结构体字面量语法可用于在不指定类型的情况下初始化联合体。

test_anonymous_union.zig
const std = @import("std");
const expect = std.testing.expect;

const Number = union {
    int: i32,
    float: f64,
};

test "anonymous union literal syntax" {
    const i: Number = .{ .int = 42 };
    const f = makeNumber();
    try expect(i.int == 42);
    try expect(f.float == 12.34);
}

fn makeNumber() Number {
    return .{ .float = 12.34 };
}
Shell
$ zig test test_anonymous_union.zig
1/1 test_anonymous_union.test.anonymous union literal syntax...OK
All 1 tests passed.

opaque §

opaque {} 声明了一个新类型,其大小和对齐方式未知(但非零)。它可以像结构体联合体枚举一样包含声明。

这通常用于与不暴露结构体细节的 C 代码交互时的类型安全。示例:

test_opaque.zig
const Derp = opaque {};
const Wat = opaque {};

extern fn bar(d: *Derp) void;
fn foo(w: *Wat) callconv(.C) void {
    bar(w);
}

test "call foo" {
    foo(undefined);
}
Shell
$ zig test test_opaque.zig
/home/andy/dev/zig/doc/langref/test_opaque.zig:6:9: error: expected type '*test_opaque.Derp', found '*test_opaque.Wat'
    bar(w);
        ^
/home/andy/dev/zig/doc/langref/test_opaque.zig:6:9: note: pointer type child 'test_opaque.Wat' cannot cast into pointer type child 'test_opaque.Derp'
/home/andy/dev/zig/doc/langref/test_opaque.zig:2:13: note: opaque declared here
const Wat = opaque {};
            ^~~~~~~~~
/home/andy/dev/zig/doc/langref/test_opaque.zig:1:14: note: opaque declared here
const Derp = opaque {};
             ^~~~~~~~~
/home/andy/dev/zig/doc/langref/test_opaque.zig:4:18: note: parameter type declared here
extern fn bar(d: *Derp) void;
                 ^~~~~
referenced by:
    test.call foo: /home/andy/dev/zig/doc/langref/test_opaque.zig:10:8

代码块 §

代码块用于限制变量声明的范围。

test_blocks.zig
test "access variable after block scope" {
    {
        var x: i32 = 1;
        _ = &x;
    }
    x += 1;
}
Shell
$ zig test test_blocks.zig
/home/andy/dev/zig/doc/langref/test_blocks.zig:6:5: error: use of undeclared identifier 'x'
    x += 1;
    ^

代码块是表达式。当带标签时,可以使用break从代码块返回一个值。

test_labeled_break.zig
const std = @import("std");
const expect = std.testing.expect;

test "labeled break from labeled block expression" {
    var y: i32 = 123;

    const x = blk: {
        y += 1;
        break :blk y;
    };
    try expect(x == 124);
    try expect(y == 124);
}
Shell
$ zig test test_labeled_break.zig
1/1 test_labeled_break.test.labeled break from labeled block expression...OK
All 1 tests passed.

这里,blk可以是任何名称。

另请参阅

变量遮蔽 §

标识符不允许通过使用相同的名称来“隐藏”其他标识符。

test_shadowing.zig
const pi = 3.14;

test "inside test block" {
    // Let's even go inside another block
    {
        var pi: i32 = 1234;
    }
}
Shell
$ zig test test_shadowing.zig
/home/andy/dev/zig/doc/langref/test_shadowing.zig:6:13: error: local variable shadows declaration of 'pi'
        var pi: i32 = 1234;
            ^~
/home/andy/dev/zig/doc/langref/test_shadowing.zig:1:1: note: declared here
const pi = 3.14;
^~~~~~~~~~~~~~~

因此,在阅读 Zig 代码时,您可以始终相信标识符在其定义范围内始终具有相同的含义。请注意,如果作用域不同,您可以使用相同的名称。

test_scopes.zig
test "separate scopes" {
    {
        const pi = 3.14;
        _ = pi;
    }
    {
        var pi: bool = true;
        _ = &pi;
    }
}
Shell
$ zig test test_scopes.zig
1/1 test_scopes.test.separate scopes...OK
All 1 tests passed.

空代码块 §

空代码块等同于void{}

test_empty_block.zig
const std = @import("std");
const expect = std.testing.expect;

test {
    const a = {};
    const b = void{};
    try expect(@TypeOf(a) == void);
    try expect(@TypeOf(b) == void);
    try expect(a == b);
}
Shell
$ zig test test_empty_block.zig
1/1 test_empty_block.test_0...OK
All 1 tests passed.

switch §

test_switch.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;

test "switch simple" {
    const a: u64 = 10;
    const zz: u64 = 103;

    // All branches of a switch expression must be able to be coerced to a
    // common type.
    //
    // Branches cannot fallthrough. If fallthrough behavior is desired, combine
    // the cases and use an if.
    const b = switch (a) {
        // Multiple cases can be combined via a ','
        1, 2, 3 => 0,

        // Ranges can be specified using the ... syntax. These are inclusive
        // of both ends.
        5...100 => 1,

        // Branches can be arbitrarily complex.
        101 => blk: {
            const c: u64 = 5;
            break :blk c * 2 + 1;
        },

        // Switching on arbitrary expressions is allowed as long as the
        // expression is known at compile-time.
        zz => zz,
        blk: {
            const d: u32 = 5;
            const e: u32 = 100;
            break :blk d + e;
        } => 107,

        // The else branch catches everything not already captured.
        // Else branches are mandatory unless the entire range of values
        // is handled.
        else => 9,
    };

    try expect(b == 1);
}

// Switch expressions can be used outside a function:
const os_msg = switch (builtin.target.os.tag) {
    .linux => "we found a linux user",
    else => "not a linux user",
};

// Inside a function, switch statements implicitly are compile-time
// evaluated if the target expression is compile-time known.
test "switch inside function" {
    switch (builtin.target.os.tag) {
        .fuchsia => {
            // On an OS other than fuchsia, block is not even analyzed,
            // so this compile error is not triggered.
            // On fuchsia this compile error would be triggered.
            @compileError("fuchsia not supported");
        },
        else => {},
    }
}
Shell
$ zig test test_switch.zig
1/2 test_switch.test.switch simple...OK
2/2 test_switch.test.switch inside function...OK
All 2 tests passed.

switch 可用于捕获带标签联合体的字段值。通过在捕获变量名前放置一个*,使其成为指针,可以修改字段值。

test_switch_tagged_union.zig
const expect = @import("std").testing.expect;

test "switch on tagged union" {
    const Point = struct {
        x: u8,
        y: u8,
    };
    const Item = union(enum) {
        a: u32,
        c: Point,
        d,
        e: u32,
    };

    var a = Item{ .c = Point{ .x = 1, .y = 2 } };

    // Switching on more complex enums is allowed.
    const b = switch (a) {
        // A capture group is allowed on a match, and will return the enum
        // value matched. If the payload types of both cases are the same
        // they can be put into the same switch prong.
        Item.a, Item.e => |item| item,

        // A reference to the matched value can be obtained using `*` syntax.
        Item.c => |*item| blk: {
            item.*.x += 1;
            break :blk 6;
        },

        // No else is required if the types cases was exhaustively handled
        Item.d => 8,
    };

    try expect(b == 6);
    try expect(a.c.x == 2);
}
Shell
$ zig test test_switch_tagged_union.zig
1/1 test_switch_tagged_union.test.switch on tagged union...OK
All 1 tests passed.

另请参阅

穷举式切换 §

当一个switch表达式没有else子句时,它必须穷举列出所有可能的值。否则将导致编译错误。

test_unhandled_enumeration_value.zig
const Color = enum {
    auto,
    off,
    on,
};

test "exhaustive switching" {
    const color = Color.off;
    switch (color) {
        Color.auto => {},
        Color.on => {},
    }
}
Shell
$ zig test test_unhandled_enumeration_value.zig
/home/andy/dev/zig/doc/langref/test_unhandled_enumeration_value.zig:9:5: error: switch must handle all possibilities
    switch (color) {
    ^~~~~~
/home/andy/dev/zig/doc/langref/test_unhandled_enumeration_value.zig:3:5: note: unhandled enumeration value: 'off'
    off,
    ^~~
/home/andy/dev/zig/doc/langref/test_unhandled_enumeration_value.zig:1:15: note: enum 'test_unhandled_enumeration_value.Color' declared here
const Color = enum {
              ^~~~

使用枚举字面量进行切换 §

枚举字面量switch结合使用会非常有用,可以避免重复指定枚举联合体类型。

test_exhaustive_switch.zig
const std = @import("std");
const expect = std.testing.expect;

const Color = enum {
    auto,
    off,
    on,
};

test "enum literals with switch" {
    const color = Color.off;
    const result = switch (color) {
        .auto => false,
        .on => false,
        .off => true,
    };
    try expect(result);
}
Shell
$ zig test test_exhaustive_switch.zig
1/1 test_exhaustive_switch.test.enum literals with switch...OK
All 1 tests passed.

带标签的 switch §

当 switch 语句被标记时,可以从 breakcontinue 中引用它。break 将从 switch 返回一个值。

针对 switch 的 continue 必须带有一个操作数。执行时,它将跳转到匹配的分支,就像 switch 再次执行时,continue 的操作数替换了初始的 switch 值。

test_switch_continue.zig
const std = @import("std");

test "switch continue" {
    sw: switch (@as(i32, 5)) {
        5 => continue :sw 4,

        // `continue` can occur multiple times within a single switch prong.
        2...4 => |v| {
            if (v > 3) {
                continue :sw 2;
            } else if (v == 3) {

                // `break` can target labeled loops.
                break :sw;
            }

            continue :sw 1;
        },

        1 => return,

        else => unreachable,
    }
}
Shell
$ zig test test_switch_continue.zig
1/1 test_switch_continue.test.switch continue...OK
All 1 tests passed.

从语义上讲,这等同于以下循环:

test_switch_continue_equivalent.zig
const std = @import("std");

test "switch continue, equivalent loop" {
    var sw: i32 = 5;
    while (true) {
        switch (sw) {
            5 => {
                sw = 4;
                continue;
            },
            2...4 => |v| {
                if (v > 3) {
                    sw = 2;
                    continue;
                } else if (v == 3) {
                    break;
                }

                sw = 1;
                continue;
            },
            1 => return,
            else => unreachable,
        }
    }
}
Shell
$ zig test test_switch_continue_equivalent.zig
1/1 test_switch_continue_equivalent.test.switch continue, equivalent loop...OK
All 1 tests passed.

这可以提高(例如)状态机的清晰度,其中语法continue :sw .next_state是明确、显式且易于理解的。

然而,最初的例子是对数组中每个元素进行切换,在这种情况下,使用单个 switch 可以提高清晰度和性能。

test_switch_dispatch_loop.zig
const std = @import("std");
const expectEqual = std.testing.expectEqual;

const Instruction = enum {
    add,
    mul,
    end,
};

fn evaluate(initial_stack: []const i32, code: []const Instruction) !i32 {
    var stack = try std.BoundedArray(i32, 8).fromSlice(initial_stack);
    var ip: usize = 0;

    return vm: switch (code[ip]) {
        // Because all code after `continue` is unreachable, this branch does
        // not provide a result.
        .add => {
            try stack.append(stack.pop().? + stack.pop().?);

            ip += 1;
            continue :vm code[ip];
        },
        .mul => {
            try stack.append(stack.pop().? * stack.pop().?);

            ip += 1;
            continue :vm code[ip];
        },
        .end => stack.pop().?,
    };
}

test "evaluate" {
    const result = try evaluate(&.{ 7, 2, -3 }, &.{ .mul, .add, .end });
    try expectEqual(1, result);
}
Shell
$ zig test test_switch_dispatch_loop.zig
1/1 test_switch_dispatch_loop.test.evaluate...OK
All 1 tests passed.

如果continue的操作数是编译期已知的,那么它可以被降级为无条件分支到相关的case。这样的分支是完全可预测的,因此执行速度通常非常快。

如果操作数是运行时已知的,每个continue都可以在行内嵌入一个条件分支(理想情况下通过跳转表),这允许CPU独立于任何其他分支预测其目标。基于循环的降级会强制每个分支通过相同的调度点,从而阻碍分支预测。

内联 switch 分支 §

Switch 分支可以标记为inline,以便为每个可能的值生成分支的主体,从而使捕获的值在编译期可知。

test_inline_switch.zig
const std = @import("std");
const expect = std.testing.expect;
const expectError = std.testing.expectError;

fn isFieldOptional(comptime T: type, field_index: usize) !bool {
    const fields = @typeInfo(T).@"struct".fields;
    return switch (field_index) {
        // This prong is analyzed twice with `idx` being a
        // comptime-known value each time.
        inline 0, 1 => |idx| @typeInfo(fields[idx].type) == .optional,
        else => return error.IndexOutOfBounds,
    };
}

const Struct1 = struct { a: u32, b: ?u32 };

test "using @typeInfo with runtime values" {
    var index: usize = 0;
    try expect(!try isFieldOptional(Struct1, index));
    index += 1;
    try expect(try isFieldOptional(Struct1, index));
    index += 1;
    try expectError(error.IndexOutOfBounds, isFieldOptional(Struct1, index));
}

// Calls to `isFieldOptional` on `Struct1` get unrolled to an equivalent
// of this function:
fn isFieldOptionalUnrolled(field_index: usize) !bool {
    return switch (field_index) {
        0 => false,
        1 => true,
        else => return error.IndexOutOfBounds,
    };
}
Shell
$ zig test test_inline_switch.zig
1/1 test_inline_switch.test.using @typeInfo with runtime values...OK
All 1 tests passed.

inline关键字也可以与范围结合使用。

inline_prong_range.zig
fn isFieldOptional(comptime T: type, field_index: usize) !bool {
    const fields = @typeInfo(T).@"struct".fields;
    return switch (field_index) {
        inline 0...fields.len - 1 => |idx| @typeInfo(fields[idx].type) == .optional,
        else => return error.IndexOutOfBounds,
    };
}

inline else分支可以用作inline for循环的类型安全替代方案。

test_inline_else.zig
const std = @import("std");
const expect = std.testing.expect;

const SliceTypeA = extern struct {
    len: usize,
    ptr: [*]u32,
};
const SliceTypeB = extern struct {
    ptr: [*]SliceTypeA,
    len: usize,
};
const AnySlice = union(enum) {
    a: SliceTypeA,
    b: SliceTypeB,
    c: []const u8,
    d: []AnySlice,
};

fn withFor(any: AnySlice) usize {
    const Tag = @typeInfo(AnySlice).@"union".tag_type.?;
    inline for (@typeInfo(Tag).@"enum".fields) |field| {
        // With `inline for` the function gets generated as
        // a series of `if` statements relying on the optimizer
        // to convert it to a switch.
        if (field.value == @intFromEnum(any)) {
            return @field(any, field.name).len;
        }
    }
    // When using `inline for` the compiler doesn't know that every
    // possible case has been handled requiring an explicit `unreachable`.
    unreachable;
}

fn withSwitch(any: AnySlice) usize {
    return switch (any) {
        // With `inline else` the function is explicitly generated
        // as the desired switch and the compiler can check that
        // every possible case is handled.
        inline else => |slice| slice.len,
    };
}

test "inline for and inline else similarity" {
    const any = AnySlice{ .c = "hello" };
    try expect(withFor(any) == 5);
    try expect(withSwitch(any) == 5);
}
Shell
$ zig test test_inline_else.zig
1/1 test_inline_else.test.inline for and inline else similarity...OK
All 1 tests passed.

当对联合体使用内联分支进行切换时,可以使用额外的捕获来获取联合体的枚举标签值。

test_inline_switch_union_tag.zig
const std = @import("std");
const expect = std.testing.expect;

const U = union(enum) {
    a: u32,
    b: f32,
};

fn getNum(u: U) u32 {
    switch (u) {
        // Here `num` is a runtime-known value that is either
        // `u.a` or `u.b` and `tag` is `u`'s comptime-known tag value.
        inline else => |num, tag| {
            if (tag == .b) {
                return @intFromFloat(num);
            }
            return num;
        },
    }
}

test "test" {
    const u = U{ .b = 42 };
    try expect(getNum(u) == 42);
}
Shell
$ zig test test_inline_switch_union_tag.zig
1/1 test_inline_switch_union_tag.test.test...OK
All 1 tests passed.

另请参阅

while §

while 循环用于重复执行表达式,直到某个条件不再为真。

test_while.zig
const expect = @import("std").testing.expect;

test "while basic" {
    var i: usize = 0;
    while (i < 10) {
        i += 1;
    }
    try expect(i == 10);
}
Shell
$ zig test test_while.zig
1/1 test_while.test.while basic...OK
All 1 tests passed.

使用break提前退出while循环。

test_while_break.zig
const expect = @import("std").testing.expect;

test "while break" {
    var i: usize = 0;
    while (true) {
        if (i == 10)
            break;
        i += 1;
    }
    try expect(i == 10);
}
Shell
$ zig test test_while_break.zig
1/1 test_while_break.test.while break...OK
All 1 tests passed.

使用continue跳回循环的开头。

test_while_continue.zig
const expect = @import("std").testing.expect;

test "while continue" {
    var i: usize = 0;
    while (true) {
        i += 1;
        if (i < 10)
            continue;
        break;
    }
    try expect(i == 10);
}
Shell
$ zig test test_while_continue.zig
1/1 test_while_continue.test.while continue...OK
All 1 tests passed.

While 循环支持一个 continue 表达式,当循环继续时该表达式会被执行。continue 关键字遵循此表达式。

test_while_continue_expression.zig
const expect = @import("std").testing.expect;

test "while loop continue expression" {
    var i: usize = 0;
    while (i < 10) : (i += 1) {}
    try expect(i == 10);
}

test "while loop continue expression, more complicated" {
    var i: usize = 1;
    var j: usize = 1;
    while (i * j < 2000) : ({
        i *= 2;
        j *= 3;
    }) {
        const my_ij = i * j;
        try expect(my_ij < 2000);
    }
}
Shell
$ zig test test_while_continue_expression.zig
1/2 test_while_continue_expression.test.while loop continue expression...OK
2/2 test_while_continue_expression.test.while loop continue expression, more complicated...OK
All 2 tests passed.

While 循环是表达式。表达式的结果是 while 循环的 else 子句的结果,该子句在 while 循环的条件测试为假时执行。

break,如同return,接受一个值参数。这是while表达式的结果。当您从while循环中break时,else分支不会被评估。

test_while_else.zig
const expect = @import("std").testing.expect;

test "while else" {
    try expect(rangeHasNumber(0, 10, 5));
    try expect(!rangeHasNumber(0, 10, 15));
}

fn rangeHasNumber(begin: usize, end: usize, number: usize) bool {
    var i = begin;
    return while (i < end) : (i += 1) {
        if (i == number) {
            break true;
        }
    } else false;
}
Shell
$ zig test test_while_else.zig
1/1 test_while_else.test.while else...OK
All 1 tests passed.

带标签的 while §

while循环带标签时,它可以在嵌套循环内部被breakcontinue引用。

test_while_nested_break.zig
test "nested break" {
    outer: while (true) {
        while (true) {
            break :outer;
        }
    }
}

test "nested continue" {
    var i: usize = 0;
    outer: while (i < 10) : (i += 1) {
        while (true) {
            continue :outer;
        }
    }
}
Shell
$ zig test test_while_nested_break.zig
1/2 test_while_nested_break.test.nested break...OK
2/2 test_while_nested_break.test.nested continue...OK
All 2 tests passed.

带 Optionals 的 while §

就像if表达式一样,while循环可以将可选类型作为条件并捕获其有效载荷。当遇到null时,循环退出。

while表达式上出现|x|语法时,while条件必须是可选类型

else分支允许在可选迭代中使用。在这种情况下,它将在遇到第一个 null 值时执行。

test_while_null_capture.zig
const expect = @import("std").testing.expect;

test "while null capture" {
    var sum1: u32 = 0;
    numbers_left = 3;
    while (eventuallyNullSequence()) |value| {
        sum1 += value;
    }
    try expect(sum1 == 3);

    // null capture with an else block
    var sum2: u32 = 0;
    numbers_left = 3;
    while (eventuallyNullSequence()) |value| {
        sum2 += value;
    } else {
        try expect(sum2 == 3);
    }

    // null capture with a continue expression
    var i: u32 = 0;
    var sum3: u32 = 0;
    numbers_left = 3;
    while (eventuallyNullSequence()) |value| : (i += 1) {
        sum3 += value;
    }
    try expect(i == 3);
}

var numbers_left: u32 = undefined;
fn eventuallyNullSequence() ?u32 {
    return if (numbers_left == 0) null else blk: {
        numbers_left -= 1;
        break :blk numbers_left;
    };
}
Shell
$ zig test test_while_null_capture.zig
1/1 test_while_null_capture.test.while null capture...OK
All 1 tests passed.

带 Error Unions 的 while §

就像if表达式一样,while循环可以将错误联合体作为条件,并捕获有效载荷或错误码。当条件导致错误码时,else分支被评估,循环结束。

while表达式中存在else |x|语法时,while条件必须是错误联合体类型

test_while_error_capture.zig
const expect = @import("std").testing.expect;

test "while error union capture" {
    var sum1: u32 = 0;
    numbers_left = 3;
    while (eventuallyErrorSequence()) |value| {
        sum1 += value;
    } else |err| {
        try expect(err == error.ReachedZero);
    }
}

var numbers_left: u32 = undefined;

fn eventuallyErrorSequence() anyerror!u32 {
    return if (numbers_left == 0) error.ReachedZero else blk: {
        numbers_left -= 1;
        break :blk numbers_left;
    };
}
Shell
$ zig test test_while_error_capture.zig
1/1 test_while_error_capture.test.while error union capture...OK
All 1 tests passed.

inline while §

While 循环可以内联。这会导致循环展开,从而使代码能够执行一些只在编译时才起作用的操作,例如将类型用作一等公民值。

test_inline_while.zig
const expect = @import("std").testing.expect;

test "inline while loop" {
    comptime var i = 0;
    var sum: usize = 0;
    inline while (i < 3) : (i += 1) {
        const T = switch (i) {
            0 => f32,
            1 => i8,
            2 => bool,
            else => unreachable,
        };
        sum += typeNameLength(T);
    }
    try expect(sum == 9);
}

fn typeNameLength(comptime T: type) usize {
    return @typeName(T).len;
}
Shell
$ zig test test_inline_while.zig
1/1 test_inline_while.test.inline while loop...OK
All 1 tests passed.

建议仅出于以下原因之一使用inline循环:

  • 你需要循环在编译期执行,以使语义生效。
  • 你有一个基准测试证明,以这种方式强制展开循环可以显著提高速度。

另请参阅

for §

test_for.zig
const expect = @import("std").testing.expect;

test "for basics" {
    const items = [_]i32{ 4, 5, 3, 4, 0 };
    var sum: i32 = 0;

    // For loops iterate over slices and arrays.
    for (items) |value| {
        // Break and continue are supported.
        if (value == 0) {
            continue;
        }
        sum += value;
    }
    try expect(sum == 16);

    // To iterate over a portion of a slice, reslice.
    for (items[0..1]) |value| {
        sum += value;
    }
    try expect(sum == 20);

    // To access the index of iteration, specify a second condition as well
    // as a second capture value.
    var sum2: i32 = 0;
    for (items, 0..) |_, i| {
        try expect(@TypeOf(i) == usize);
        sum2 += @as(i32, @intCast(i));
    }
    try expect(sum2 == 10);

    // To iterate over consecutive integers, use the range syntax.
    // Unbounded range is always a compile error.
    var sum3: usize = 0;
    for (0..5) |i| {
        sum3 += i;
    }
    try expect(sum3 == 10);
}

test "multi object for" {
    const items = [_]usize{ 1, 2, 3 };
    const items2 = [_]usize{ 4, 5, 6 };
    var count: usize = 0;

    // Iterate over multiple objects.
    // All lengths must be equal at the start of the loop, otherwise detectable
    // illegal behavior occurs.
    for (items, items2) |i, j| {
        count += i + j;
    }

    try expect(count == 21);
}

test "for reference" {
    var items = [_]i32{ 3, 4, 2 };

    // Iterate over the slice by reference by
    // specifying that the capture value is a pointer.
    for (&items) |*value| {
        value.* += 1;
    }

    try expect(items[0] == 4);
    try expect(items[1] == 5);
    try expect(items[2] == 3);
}

test "for else" {
    // For allows an else attached to it, the same as a while loop.
    const items = [_]?i32{ 3, 4, null, 5 };

    // For loops can also be used as expressions.
    // Similar to while loops, when you break from a for loop, the else branch is not evaluated.
    var sum: i32 = 0;
    const result = for (items) |value| {
        if (value != null) {
            sum += value.?;
        }
    } else blk: {
        try expect(sum == 12);
        break :blk sum;
    };
    try expect(result == 12);
}
Shell
$ zig test test_for.zig
1/4 test_for.test.for basics...OK
2/4 test_for.test.multi object for...OK
3/4 test_for.test.for reference...OK
4/4 test_for.test.for else...OK
All 4 tests passed.

带标签的 for §

for循环带标签时,它可以在嵌套循环内部被breakcontinue引用。

test_for_nested_break.zig
const std = @import("std");
const expect = std.testing.expect;

test "nested break" {
    var count: usize = 0;
    outer: for (1..6) |_| {
        for (1..6) |_| {
            count += 1;
            break :outer;
        }
    }
    try expect(count == 1);
}

test "nested continue" {
    var count: usize = 0;
    outer: for (1..9) |_| {
        for (1..6) |_| {
            count += 1;
            continue :outer;
        }
    }

    try expect(count == 8);
}
Shell
$ zig test test_for_nested_break.zig
1/2 test_for_nested_break.test.nested break...OK
2/2 test_for_nested_break.test.nested continue...OK
All 2 tests passed.

inline for §

For 循环可以内联。这会导致循环展开,从而使代码能够执行一些只在编译时才起作用的操作,例如将类型用作一等公民值。内联 for 循环的捕获值和迭代器值在编译时已知。

test_inline_for.zig
const expect = @import("std").testing.expect;

test "inline for loop" {
    const nums = [_]i32{ 2, 4, 6 };
    var sum: usize = 0;
    inline for (nums) |i| {
        const T = switch (i) {
            2 => f32,
            4 => i8,
            6 => bool,
            else => unreachable,
        };
        sum += typeNameLength(T);
    }
    try expect(sum == 9);
}

fn typeNameLength(comptime T: type) usize {
    return @typeName(T).len;
}
Shell
$ zig test test_inline_for.zig
1/1 test_inline_for.test.inline for loop...OK
All 1 tests passed.

建议仅出于以下原因之一使用inline循环:

  • 你需要循环在编译期执行,以使语义生效。
  • 你有一个基准测试证明,以这种方式强制展开循环可以显著提高速度。

另请参阅

if §

test_if.zig
// If expressions have three uses, corresponding to the three types:
// * bool
// * ?T
// * anyerror!T

const expect = @import("std").testing.expect;

test "if expression" {
    // If expressions are used instead of a ternary expression.
    const a: u32 = 5;
    const b: u32 = 4;
    const result = if (a != b) 47 else 3089;
    try expect(result == 47);
}

test "if boolean" {
    // If expressions test boolean conditions.
    const a: u32 = 5;
    const b: u32 = 4;
    if (a != b) {
        try expect(true);
    } else if (a == 9) {
        unreachable;
    } else {
        unreachable;
    }
}

test "if error union" {
    // If expressions test for errors.
    // Note the |err| capture on the else.

    const a: anyerror!u32 = 0;
    if (a) |value| {
        try expect(value == 0);
    } else |err| {
        _ = err;
        unreachable;
    }

    const b: anyerror!u32 = error.BadValue;
    if (b) |value| {
        _ = value;
        unreachable;
    } else |err| {
        try expect(err == error.BadValue);
    }

    // The else and |err| capture is strictly required.
    if (a) |value| {
        try expect(value == 0);
    } else |_| {}

    // To check only the error value, use an empty block expression.
    if (b) |_| {} else |err| {
        try expect(err == error.BadValue);
    }

    // Access the value by reference using a pointer capture.
    var c: anyerror!u32 = 3;
    if (c) |*value| {
        value.* = 9;
    } else |_| {
        unreachable;
    }

    if (c) |value| {
        try expect(value == 9);
    } else |_| {
        unreachable;
    }
}
Shell
$ zig test test_if.zig
1/3 test_if.test.if expression...OK
2/3 test_if.test.if boolean...OK
3/3 test_if.test.if error union...OK
All 3 tests passed.

带 Optionals 的 if §

test_if_optionals.zig
const expect = @import("std").testing.expect;

test "if optional" {
    // If expressions test for null.

    const a: ?u32 = 0;
    if (a) |value| {
        try expect(value == 0);
    } else {
        unreachable;
    }

    const b: ?u32 = null;
    if (b) |_| {
        unreachable;
    } else {
        try expect(true);
    }

    // The else is not required.
    if (a) |value| {
        try expect(value == 0);
    }

    // To test against null only, use the binary equality operator.
    if (b == null) {
        try expect(true);
    }

    // Access the value by reference using a pointer capture.
    var c: ?u32 = 3;
    if (c) |*value| {
        value.* = 2;
    }

    if (c) |value| {
        try expect(value == 2);
    } else {
        unreachable;
    }
}

test "if error union with optional" {
    // If expressions test for errors before unwrapping optionals.
    // The |optional_value| capture's type is ?u32.

    const a: anyerror!?u32 = 0;
    if (a) |optional_value| {
        try expect(optional_value.? == 0);
    } else |err| {
        _ = err;
        unreachable;
    }

    const b: anyerror!?u32 = null;
    if (b) |optional_value| {
        try expect(optional_value == null);
    } else |_| {
        unreachable;
    }

    const c: anyerror!?u32 = error.BadValue;
    if (c) |optional_value| {
        _ = optional_value;
        unreachable;
    } else |err| {
        try expect(err == error.BadValue);
    }

    // Access the value by reference by using a pointer capture each time.
    var d: anyerror!?u32 = 3;
    if (d) |*optional_value| {
        if (optional_value.*) |*value| {
            value.* = 9;
        }
    } else |_| {
        unreachable;
    }

    if (d) |optional_value| {
        try expect(optional_value.? == 9);
    } else |_| {
        unreachable;
    }
}
Shell
$ zig test test_if_optionals.zig
1/2 test_if_optionals.test.if optional...OK
2/2 test_if_optionals.test.if error union with optional...OK
All 2 tests passed.

另请参阅

defer §

在作用域退出时无条件执行表达式。

test_defer.zig
const std = @import("std");
const expect = std.testing.expect;
const print = std.debug.print;

fn deferExample() !usize {
    var a: usize = 1;

    {
        defer a = 2;
        a = 1;
    }
    try expect(a == 2);

    a = 5;
    return a;
}

test "defer basics" {
    try expect((try deferExample()) == 5);
}
Shell
$ zig test test_defer.zig
1/1 test_defer.test.defer basics...OK
All 1 tests passed.

延迟表达式按相反顺序评估。

defer_unwind.zig
const std = @import("std");
const expect = std.testing.expect;
const print = std.debug.print;

test "defer unwinding" {
    print("\n", .{});

    defer {
        print("1 ", .{});
    }
    defer {
        print("2 ", .{});
    }
    if (false) {
        // defers are not run if they are never executed.
        defer {
            print("3 ", .{});
        }
    }
}
Shell
$ zig test defer_unwind.zig
1/1 defer_unwind.test.defer unwinding...
2 1 OK
All 1 tests passed.

在 defer 表达式中不允许使用 return 语句。

test_invalid_defer.zig
fn deferInvalidExample() !void {
    defer {
        return error.DeferError;
    }

    return error.DeferError;
}
Shell
$ zig test test_invalid_defer.zig
/home/andy/dev/zig/doc/langref/test_invalid_defer.zig:3:9: error: cannot return from defer expression
        return error.DeferError;
        ^~~~~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_invalid_defer.zig:2:5: note: defer expression here
    defer {
    ^~~~~

另请参阅

unreachable §

DebugReleaseSafe模式下,unreachable会发出对panic的调用,并附带消息reached unreachable code

ReleaseFastReleaseSmall模式下,优化器利用unreachable代码永远不会被触及的假设来执行优化。

基础 §

test_unreachable.zig
// unreachable is used to assert that control flow will never reach a
// particular location:
test "basic math" {
    const x = 1;
    const y = 2;
    if (x + y != 3) {
        unreachable;
    }
}
Shell
$ zig test test_unreachable.zig
1/1 test_unreachable.test.basic math...OK
All 1 tests passed.

事实上,std.debug.assert就是这样实现的。

test_assertion_failure.zig
// This is how std.debug.assert is implemented
fn assert(ok: bool) void {
    if (!ok) unreachable; // assertion failure
}

// This test will fail because we hit unreachable.
test "this will fail" {
    assert(false);
}
Shell
$ zig test test_assertion_failure.zig
1/1 test_assertion_failure.test.this will fail...thread 209597 panic: reached unreachable code
/home/andy/dev/zig/doc/langref/test_assertion_failure.zig:3:14: 0x104892d in assert (test)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/dev/zig/doc/langref/test_assertion_failure.zig:8:11: 0x10488fa in test.this will fail (test)
    assert(false);
          ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef0c5 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e74ad in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e6922 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e64fd in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/0594f73810636b148c5e4293efd1faf6/test --seed=0x2a37766c

编译期 §

test_comptime_unreachable.zig
const assert = @import("std").debug.assert;

test "type of unreachable" {
    comptime {
        // The type of unreachable is noreturn.

        // However this assertion will still fail to compile because
        // unreachable expressions are compile errors.

        assert(@TypeOf(unreachable) == noreturn);
    }
}
Shell
$ zig test test_comptime_unreachable.zig
/home/andy/dev/zig/doc/langref/test_comptime_unreachable.zig:10:16: error: unreachable code
        assert(@TypeOf(unreachable) == noreturn);
               ^~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_comptime_unreachable.zig:10:24: note: control flow is diverted here
        assert(@TypeOf(unreachable) == noreturn);
                       ^~~~~~~~~~~

另请参阅

noreturn §

noreturn的类型是:

  • break
  • continue
  • return
  • unreachable (不可达)
  • while (true) {}

当类型一起解析时,例如if子句或switch分支,noreturn类型与所有其他类型兼容。考虑:

test_noreturn.zig
fn foo(condition: bool, b: u32) void {
    const a = if (condition) b else return;
    _ = a;
    @panic("do something with a");
}
test "noreturn" {
    foo(false, 1);
}
Shell
$ zig test test_noreturn.zig
1/1 test_noreturn.test.noreturn...OK
All 1 tests passed.

noreturn的另一个用例是exit函数:

test_noreturn_from_exit.zig
const std = @import("std");
const builtin = @import("builtin");
const native_arch = builtin.cpu.arch;
const expect = std.testing.expect;

const WINAPI: std.builtin.CallingConvention = if (native_arch == .x86) .Stdcall else .C;
extern "kernel32" fn ExitProcess(exit_code: c_uint) callconv(WINAPI) noreturn;

test "foo" {
    const value = bar() catch ExitProcess(1);
    try expect(value == 1234);
}

fn bar() anyerror!u32 {
    return 1234;
}
Shell
$ zig test test_noreturn_from_exit.zig -target x86_64-windows --test-no-exec

函数 §

test_functions.zig
const std = @import("std");
const builtin = @import("builtin");
const native_arch = builtin.cpu.arch;
const expect = std.testing.expect;

// Functions are declared like this
fn add(a: i8, b: i8) i8 {
    if (a == 0) {
        return b;
    }

    return a + b;
}

// The export specifier makes a function externally visible in the generated
// object file, and makes it use the C ABI.
export fn sub(a: i8, b: i8) i8 {
    return a - b;
}

// The extern specifier is used to declare a function that will be resolved
// at link time, when linking statically, or at runtime, when linking
// dynamically. The quoted identifier after the extern keyword specifies
// the library that has the function. (e.g. "c" -> libc.so)
// The callconv specifier changes the calling convention of the function.
const WINAPI: std.builtin.CallingConvention = if (native_arch == .x86) .Stdcall else .C;
extern "kernel32" fn ExitProcess(exit_code: u32) callconv(WINAPI) noreturn;
extern "c" fn atan2(a: f64, b: f64) f64;

// The @branchHint builtin can be used to tell the optimizer that a function is rarely called ("cold").
fn abort() noreturn {
    @branchHint(.cold);
    while (true) {}
}

// The naked calling convention makes a function not have any function prologue or epilogue.
// This can be useful when integrating with assembly.
fn _start() callconv(.Naked) noreturn {
    abort();
}

// The inline calling convention forces a function to be inlined at all call sites.
// If the function cannot be inlined, it is a compile-time error.
inline fn shiftLeftOne(a: u32) u32 {
    return a << 1;
}

// The pub specifier allows the function to be visible when importing.
// Another file can use @import and call sub2
pub fn sub2(a: i8, b: i8) i8 {
    return a - b;
}

// Function pointers are prefixed with `*const `.
const Call2Op = *const fn (a: i8, b: i8) i8;
fn doOp(fnCall: Call2Op, op1: i8, op2: i8) i8 {
    return fnCall(op1, op2);
}

test "function" {
    try expect(doOp(add, 5, 6) == 11);
    try expect(doOp(sub2, 5, 6) == -1);
}
Shell
$ zig test test_functions.zig
1/1 test_functions.test.function...OK
All 1 tests passed.

函数和函数指针之间存在差异。函数体是编译时类型,而函数指针可能是运行时已知类型。

值传递参数 §

作为参数传递的整数浮点数等基本类型会被复制,然后该副本在函数体中可用。这称为“值传递”。复制基本类型本质上是免费的,通常只涉及设置寄存器。

结构体、联合体和数组有时可以更有效地作为引用传递,因为根据大小,复制的开销可能任意大。当这些类型作为参数传递时,Zig可能会选择复制并按值传递,或者按引用传递,以Zig认为更快的任何方式进行。这部分归因于参数是不可变的这一事实。

test_pass_by_reference_or_value.zig
const Point = struct {
    x: i32,
    y: i32,
};

fn foo(point: Point) i32 {
    // Here, `point` could be a reference, or a copy. The function body
    // can ignore the difference and treat it as a value. Be very careful
    // taking the address of the parameter - it should be treated as if
    // the address will become invalid when the function returns.
    return point.x + point.y;
}

const expect = @import("std").testing.expect;

test "pass struct to function" {
    try expect(foo(Point{ .x = 1, .y = 2 }) == 3);
}
Shell
$ zig test test_pass_by_reference_or_value.zig
1/1 test_pass_by_reference_or_value.test.pass struct to function...OK
All 1 tests passed.

对于外部函数,Zig 遵循 C ABI 来按值传递结构体和联合体。

函数参数类型推断 §

函数参数可以使用anytype代替类型进行声明。在这种情况下,参数类型将在函数调用时被推断。使用@TypeOf@typeInfo来获取推断类型的信息。

test_fn_type_inference.zig
const expect = @import("std").testing.expect;

fn addFortyTwo(x: anytype) @TypeOf(x) {
    return x + 42;
}

test "fn type inference" {
    try expect(addFortyTwo(1) == 43);
    try expect(@TypeOf(addFortyTwo(1)) == comptime_int);
    const y: i64 = 2;
    try expect(addFortyTwo(y) == 44);
    try expect(@TypeOf(addFortyTwo(y)) == i64);
}
Shell
$ zig test test_fn_type_inference.zig
1/1 test_fn_type_inference.test.fn type inference...OK
All 1 tests passed.

inline fn §

在函数定义中添加inline关键字会使该函数在调用点成为语义上的内联。这不是优化通道可能观察到的提示,而是对函数调用中涉及的类型和值具有影响。

与普通函数调用不同,内联函数调用点处已在编译时已知参数被视为编译时参数。这可能一直传播到返回值。

inline_call.zig
test "inline function call" {
    if (foo(1200, 34) != 1234) {
        @compileError("bad");
    }
}

inline fn foo(a: i32, b: i32) i32 {
    return a + b;
}
Shell
$ zig test inline_call.zig
1/1 inline_call.test.inline function call...OK
All 1 tests passed.

如果移除了inline,测试将失败并出现编译错误,而不是通过。

通常最好让编译器决定何时内联函数,除了以下情况:

  • 为了调试目的,需要改变调用堆栈中的堆栈帧数量。
  • 强制将参数的编译时特性传播到函数的返回值,如上述示例所示。
  • 实际性能测量要求如此。

请注意,inline实际上限制了编译器可以做的事情。这可能会损害二进制大小、编译速度,甚至运行时性能。

函数反射 §

test_fn_reflection.zig
const std = @import("std");
const math = std.math;
const testing = std.testing;

test "fn reflection" {
    try testing.expect(@typeInfo(@TypeOf(testing.expect)).@"fn".params[0].type.? == bool);
    try testing.expect(@typeInfo(@TypeOf(testing.tmpDir)).@"fn".return_type.? == testing.TmpDir);

    try testing.expect(@typeInfo(@TypeOf(math.Log2Int)).@"fn".is_generic);
}
Shell
$ zig test test_fn_reflection.zig
1/1 test_fn_reflection.test.fn reflection...OK
All 1 tests passed.

错误 §

错误集类型 §

错误集就像枚举。然而,整个编译过程中的每个错误名称都会被分配一个大于 0 的无符号整数。您可以多次声明相同的错误名称,如果这样做,它将分配相同的整数值。

错误集类型默认为u16,但如果通过--error-limit [num]命令行参数提供了最大不同错误值数量,则将使用包含表示所有错误值所需最小位数整数类型。

您可以将错误从子集强制转换为超集。

test_coerce_error_subset_to_superset.zig
const std = @import("std");

const FileOpenError = error{
    AccessDenied,
    OutOfMemory,
    FileNotFound,
};

const AllocationError = error{
    OutOfMemory,
};

test "coerce subset to superset" {
    const err = foo(AllocationError.OutOfMemory);
    try std.testing.expect(err == FileOpenError.OutOfMemory);
}

fn foo(err: AllocationError) FileOpenError {
    return err;
}
Shell
$ zig test test_coerce_error_subset_to_superset.zig
1/1 test_coerce_error_subset_to_superset.test.coerce subset to superset...OK
All 1 tests passed.

但您不能将错误从超集强制转换为子集。

test_coerce_error_superset_to_subset.zig
const FileOpenError = error{
    AccessDenied,
    OutOfMemory,
    FileNotFound,
};

const AllocationError = error{
    OutOfMemory,
};

test "coerce superset to subset" {
    foo(FileOpenError.OutOfMemory) catch {};
}

fn foo(err: FileOpenError) AllocationError {
    return err;
}
Shell
$ zig test test_coerce_error_superset_to_subset.zig
/home/andy/dev/zig/doc/langref/test_coerce_error_superset_to_subset.zig:16:12: error: expected type 'error{OutOfMemory}', found 'error{AccessDenied,OutOfMemory,FileNotFound}'
    return err;
           ^~~
/home/andy/dev/zig/doc/langref/test_coerce_error_superset_to_subset.zig:16:12: note: 'error.AccessDenied' not a member of destination error set
/home/andy/dev/zig/doc/langref/test_coerce_error_superset_to_subset.zig:16:12: note: 'error.FileNotFound' not a member of destination error set
/home/andy/dev/zig/doc/langref/test_coerce_error_superset_to_subset.zig:15:28: note: function return type declared here
fn foo(err: FileOpenError) AllocationError {
                           ^~~~~~~~~~~~~~~
referenced by:
    test.coerce superset to subset: /home/andy/dev/zig/doc/langref/test_coerce_error_superset_to_subset.zig:12:8

有一种声明只包含一个值的错误集并获取该值的快捷方式:

single_value_error_set_shortcut.zig
const err = error.FileNotFound;

这等价于:

single_value_error_set.zig
const err = (error{FileNotFound}).FileNotFound;

当使用推断错误集时,这变得很有用。

全局错误集 §

anyerror 指的是全局错误集。这个错误集包含整个编译单元中的所有错误,也就是说,它是所有其他错误集的并集。

你可以将任何错误集强制转换为全局错误集,也可以将全局错误集中的错误显式转换为非全局错误集。这会插入一个语言级别的断言,以确保错误值确实在目标错误集中。

通常应避免使用全局错误集,因为它会阻止编译器在编译时知道可能出现哪些错误。在编译时知道错误集对于生成的文档和有用的错误消息(例如在switch中忘记可能的错误值)更有益。

错误联合体类型 §

错误集类型和普通类型可以通过!二元运算符组合成一个错误联合体类型。您可能比单独使用错误集类型更频繁地使用错误联合体类型。

这是一个将字符串解析为 64 位整数的函数:

error_union_parsing_u64.zig
const std = @import("std");
const maxInt = std.math.maxInt;

pub fn parseU64(buf: []const u8, radix: u8) !u64 {
    var x: u64 = 0;

    for (buf) |c| {
        const digit = charToDigit(c);

        if (digit >= radix) {
            return error.InvalidChar;
        }

        // x *= radix
        var ov = @mulWithOverflow(x, radix);
        if (ov[1] != 0) return error.OverFlow;

        // x += digit
        ov = @addWithOverflow(ov[0], digit);
        if (ov[1] != 0) return error.OverFlow;
        x = ov[0];
    }

    return x;
}

fn charToDigit(c: u8) u8 {
    return switch (c) {
        '0'...'9' => c - '0',
        'A'...'Z' => c - 'A' + 10,
        'a'...'z' => c - 'a' + 10,
        else => maxInt(u8),
    };
}

test "parse u64" {
    const result = try parseU64("1234", 10);
    try std.testing.expect(result == 1234);
}
Shell
$ zig test error_union_parsing_u64.zig
1/1 error_union_parsing_u64.test.parse u64...OK
All 1 tests passed.

请注意返回类型是!u64。这意味着函数要么返回一个无符号的64位整数,要么返回一个错误。我们省略了!左侧的错误集,因此错误集被推断出来。

在函数定义中,您可以看到一些返回错误的 return 语句,以及底部返回u64的 return 语句。两种类型都强制转换anyerror!u64

如何使用此函数取决于您要执行的操作。以下之一:

  • 如果您希望在返回错误时提供一个默认值。
  • 如果它返回了一个错误,那么您想返回相同的错误。
  • 您完全确定它不会返回错误,因此想要无条件地解包它。
  • 您希望针对每个可能的错误采取不同的操作。

catch §

如果你想提供一个默认值,你可以使用catch二元运算符:

catch.zig
const parseU64 = @import("error_union_parsing_u64.zig").parseU64;

fn doAThing(str: []u8) void {
    const number = parseU64(str, 10) catch 13;
    _ = number; // ...
}

在这段代码中,number将等于成功解析的字符串,或者默认值13。二元catch运算符右侧的类型必须与未包装的错误联合体类型匹配,或者为noreturn类型。

如果你想在执行一些逻辑后使用catch提供一个默认值,你可以将catch与具名代码块结合使用。

handle_error_with_catch_block.zig.zig
const parseU64 = @import("error_union_parsing_u64.zig").parseU64;

fn doAThing(str: []u8) void {
    const number = parseU64(str, 10) catch blk: {
        // do things
        break :blk 13;
    };
    _ = number; // number is now initialized
}

try §

假设您想在收到错误时返回错误,否则继续函数逻辑:

catch_err_return.zig
const parseU64 = @import("error_union_parsing_u64.zig").parseU64;

fn doAThing(str: []u8) !void {
    const number = parseU64(str, 10) catch |err| return err;
    _ = number; // ...
}

对此有一个快捷方式。try 表达式:

try.zig
const parseU64 = @import("error_union_parsing_u64.zig").parseU64;

fn doAThing(str: []u8) !void {
    const number = try parseU64(str, 10);
    _ = number; // ...
}

try评估一个错误联合体表达式。如果它是错误,它将以相同的错误从当前函数返回。否则,表达式结果为解包后的值。

也许你完全确定某个表达式绝不会产生错误。在这种情况下,你可以这样做:

const number = parseU64("1234", 10) catch unreachable;

这里我们确信“1234”将成功解析。所以我们将unreachable值放在右侧。unreachable会触发安全检查的非法行为,因此在DebugReleaseSafe模式下,默认会触发安全恐慌。因此,当我们在调试应用程序时,如果这里真的发生了意外错误,应用程序将适当地崩溃。

您可能希望针对每种情况采取不同的行动。为此,我们结合了ifswitch表达式:

handle_all_error_scenarios.zig
fn doAThing(str: []u8) void {
    if (parseU64(str, 10)) |number| {
        doSomethingWithNumber(number);
    } else |err| switch (err) {
        error.Overflow => {
            // handle overflow...
        },
        // we promise that InvalidChar won't happen (or crash in debug mode if it does)
        error.InvalidChar => unreachable,
    }
}

最后,您可能只想处理部分错误。为此,您可以在else情况中捕获未处理的错误,该情况现在包含一个更窄的错误集:

handle_some_error_scenarios.zig
fn doAnotherThing(str: []u8) error{InvalidChar}!void {
    if (parseU64(str, 10)) |number| {
        doSomethingWithNumber(number);
    } else |err| switch (err) {
        error.Overflow => {
            // handle overflow...
        },
        else => |leftover_err| return leftover_err,
    }
}

您必须使用变量捕获语法。如果您不需要该变量,可以使用_进行捕获并避免使用switch

handle_no_error_scenarios.zig
fn doADifferentThing(str: []u8) void {
    if (parseU64(str, 10)) |number| {
        doSomethingWithNumber(number);
    } else |_| {
        // do as you'd like
    }
}

errdefer §

错误处理的另一个组成部分是 defer 语句。除了无条件的defer之外,Zig 还有errdefer,它仅在函数因块内的错误返回时才评估延迟表达式。

示例

errdefer_example.zig
fn createFoo(param: i32) !Foo {
    const foo = try tryToAllocateFoo();
    // now we have allocated foo. we need to free it if the function fails.
    // but we want to return it if the function succeeds.
    errdefer deallocateFoo(foo);

    const tmp_buf = allocateTmpBuffer() orelse return error.OutOfMemory;
    // tmp_buf is truly a temporary resource, and we for sure want to clean it up
    // before this block leaves scope
    defer deallocateTmpBuffer(tmp_buf);

    if (param > 1337) return error.InvalidParam;

    // here the errdefer will not run since we're returning success from the function.
    // but the defer will run!
    return foo;
}

这其中的巧妙之处在于,您无需为了确保每个退出路径都被覆盖而进行冗长和认知开销大的操作,即可获得健壮的错误处理。去分配代码总是紧随分配代码之后。

errdefer语句可以可选地捕获错误。

test_errdefer_capture.zig
const std = @import("std");

fn captureError(captured: *?anyerror) !void {
    errdefer |err| {
        captured.* = err;
    }
    return error.GeneralFailure;
}

test "errdefer capture" {
    var captured: ?anyerror = null;

    if (captureError(&captured)) unreachable else |err| {
        try std.testing.expectEqual(error.GeneralFailure, captured.?);
        try std.testing.expectEqual(error.GeneralFailure, err);
    }
}
Shell
$ zig test test_errdefer_capture.zig
1/1 test_errdefer_capture.test.errdefer capture...OK
All 1 tests passed.

关于错误处理的其他一些细节:

  • 这些原语提供了足够的表达能力,使得未检查错误成为编译错误完全实用。如果您确实想忽略错误,可以添加catch unreachable,并获得额外的好处,即如果您的假设错误,则在调试和安全发布模式下会崩溃。
  • 由于 Zig 了解错误类型,它可以预先根据错误未发生的情况来调整分支权重。这只是其他语言中没有的一点优化优势。

另请参阅

错误联合体是使用!二元运算符创建的。您可以使用编译时反射来访问错误联合体的子类型。

test_error_union.zig
const expect = @import("std").testing.expect;

test "error union" {
    var foo: anyerror!i32 = undefined;

    // Coerce from child type of an error union:
    foo = 1234;

    // Coerce from an error set:
    foo = error.SomeError;

    // Use compile-time reflection to access the payload type of an error union:
    try comptime expect(@typeInfo(@TypeOf(foo)).error_union.payload == i32);

    // Use compile-time reflection to access the error set type of an error union:
    try comptime expect(@typeInfo(@TypeOf(foo)).error_union.error_set == anyerror);
}
Shell
$ zig test test_error_union.zig
1/1 test_error_union.test.error union...OK
All 1 tests passed.

合并错误集 §

使用||运算符合并两个错误集。结果错误集包含两个错误集中的错误。左侧的文档注释会覆盖右侧的文档注释。在此示例中,C.PathNotFound的文档注释是A doc comment

这对于根据编译期分支返回不同错误集的函数特别有用。例如,Zig 标准库使用LinuxFileOpenError || WindowsFileOpenError作为打开文件的错误集。

test_merging_error_sets.zig
const A = error{
    NotDir,

    /// A doc comment
    PathNotFound,
};
const B = error{
    OutOfMemory,

    /// B doc comment
    PathNotFound,
};

const C = A || B;

fn foo() C!void {
    return error.NotDir;
}

test "merge error sets" {
    if (foo()) {
        @panic("unexpected");
    } else |err| switch (err) {
        error.OutOfMemory => @panic("unexpected"),
        error.PathNotFound => @panic("unexpected"),
        error.NotDir => {},
    }
}
Shell
$ zig test test_merging_error_sets.zig
1/1 test_merging_error_sets.test.merge error sets...OK
All 1 tests passed.

推断错误集 §

由于 Zig 中的许多函数可能会返回错误,Zig 支持推断错误集。要推断函数的错误集,请在函数返回类型前加上!运算符,例如!T

test_inferred_error_sets.zig
// With an inferred error set
pub fn add_inferred(comptime T: type, a: T, b: T) !T {
    const ov = @addWithOverflow(a, b);
    if (ov[1] != 0) return error.Overflow;
    return ov[0];
}

// With an explicit error set
pub fn add_explicit(comptime T: type, a: T, b: T) Error!T {
    const ov = @addWithOverflow(a, b);
    if (ov[1] != 0) return error.Overflow;
    return ov[0];
}

const Error = error{
    Overflow,
};

const std = @import("std");

test "inferred error set" {
    if (add_inferred(u8, 255, 1)) |_| unreachable else |err| switch (err) {
        error.Overflow => {}, // ok
    }
}
Shell
$ zig test test_inferred_error_sets.zig
1/1 test_inferred_error_sets.test.inferred error set...OK
All 1 tests passed.

当函数具有推断错误集时,该函数将变为泛型,因此对其执行某些操作会变得更加棘手,例如获取函数指针或在不同构建目标之间保持错误集一致。此外,推断错误集与递归不兼容。

在这些情况下,建议使用显式错误集。您通常可以从一个空的错误集开始,并让编译错误引导您完成该集的构建。

这些限制可能会在 Zig 的未来版本中得到克服。

错误返回追踪 §

错误返回追踪显示了错误返回给调用函数的所有代码点。这使得在任何地方使用try变得实用,并且如果错误最终冒泡到应用程序之外,仍然能够知道发生了什么。

error_return_trace.zig
pub fn main() !void {
    try foo(12);
}

fn foo(x: i32) !void {
    if (x >= 5) {
        try bar();
    } else {
        try bang2();
    }
}

fn bar() !void {
    if (baz()) {
        try quux();
    } else |err| switch (err) {
        error.FileNotFound => try hello(),
    }
}

fn baz() !void {
    try bang1();
}

fn quux() !void {
    try bang2();
}

fn hello() !void {
    try bang2();
}

fn bang1() !void {
    return error.FileNotFound;
}

fn bang2() !void {
    return error.PermissionDenied;
}
Shell
$ zig build-exe error_return_trace.zig
$ ./error_return_trace
error: PermissionDenied
/home/andy/dev/zig/doc/langref/error_return_trace.zig:34:5: 0x10de708 in bang1 (error_return_trace)
    return error.FileNotFound;
    ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:22:5: 0x10de733 in baz (error_return_trace)
    try bang1();
    ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:38:5: 0x10de758 in bang2 (error_return_trace)
    return error.PermissionDenied;
    ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:30:5: 0x10de7c3 in hello (error_return_trace)
    try bang2();
    ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:17:31: 0x10de868 in bar (error_return_trace)
        error.FileNotFound => try hello(),
                              ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:7:9: 0x10de8d0 in foo (error_return_trace)
        try bar();
        ^
/home/andy/dev/zig/doc/langref/error_return_trace.zig:2:5: 0x10de928 in main (error_return_trace)
    try foo(12);
    ^

仔细看这个例子。这不是堆栈追踪。

您可以看到最终冒泡的错误是PermissionDenied,但导致这一切的原始错误是FileNotFound。在bar函数中,代码处理原始错误码,然后从 switch 语句返回另一个错误码。错误返回追踪清楚地说明了这一点,而堆栈追踪看起来会像这样:

stack_trace.zig
pub fn main() void {
    foo(12);
}

fn foo(x: i32) void {
    if (x >= 5) {
        bar();
    } else {
        bang2();
    }
}

fn bar() void {
    if (baz()) {
        quux();
    } else {
        hello();
    }
}

fn baz() bool {
    return bang1();
}

fn quux() void {
    bang2();
}

fn hello() void {
    bang2();
}

fn bang1() bool {
    return false;
}

fn bang2() void {
    @panic("PermissionDenied");
}
Shell
$ zig build-exe stack_trace.zig
$ ./stack_trace
thread 207080 panic: PermissionDenied
/home/andy/dev/zig/doc/langref/stack_trace.zig:38:5: 0x10df3dc in bang2 (stack_trace)
    @panic("PermissionDenied");
    ^
/home/andy/dev/zig/doc/langref/stack_trace.zig:30:10: 0x10dfca8 in hello (stack_trace)
    bang2();
         ^
/home/andy/dev/zig/doc/langref/stack_trace.zig:17:14: 0x10df3b0 in bar (stack_trace)
        hello();
             ^
/home/andy/dev/zig/doc/langref/stack_trace.zig:7:12: 0x10df1d4 in foo (stack_trace)
        bar();
           ^
/home/andy/dev/zig/doc/langref/stack_trace.zig:2:8: 0x10de96d in main (stack_trace)
    foo(12);
       ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de362 in posixCallMainAndExit (stack_trace)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf3d in _start (stack_trace)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

这里,堆栈追踪并没有解释bar中的控制流如何到达hello()调用。为了找出原因,需要打开调试器或进一步检测应用程序。另一方面,错误返回追踪准确地显示了错误是如何冒泡的。

此调试功能使得快速迭代能够健壮地处理所有错误条件的代码变得更容易。这意味着 Zig 开发者将自然而然地编写正确、健壮的代码,以提高开发速度。

错误返回追踪默认在DebugReleaseSafe构建中启用,在ReleaseFastReleaseSmall构建中禁用。

有几种方法可以激活此错误返回追踪功能:

  • 从 main 返回一个错误。
  • 一个错误到达catch unreachable,并且您尚未覆盖默认的 panic 处理程序。
  • 使用errorReturnTrace访问当前返回追踪。您可以使用std.debug.dumpStackTrace打印它。在没有错误返回追踪支持的情况下构建时,此函数返回编译时已知的null

实现细节 §

要分析性能成本,有两种情况:

  • 当没有错误返回时
  • 当返回错误时

在没有错误返回的情况下,成本是单次内存写入操作,仅在调用可失败函数的调用图中的第一个不可失败函数中发生,即当返回void的函数调用返回error的函数时。这是为了在栈内存中初始化这个结构体:

stack_trace_struct.zig
pub const StackTrace = struct {
    index: usize,
    instruction_addresses: [N]usize,
};

这里,N是由调用图分析确定的最大函数调用深度。递归被忽略,并计为2。

一个指向StackTrace的指针作为秘密参数传递给每个可能返回错误的函数,但它总是第一个参数,所以它很可能停留在寄存器中。

这就是没有错误发生时的路径。在性能方面,它几乎是免费的。

当生成返回错误的函数的代码时,就在return语句之前(仅针对返回错误的return语句),Zig 生成对此函数的调用:

zig_return_error_fn.zig
// marked as "no-inline" in LLVM IR
fn __zig_return_error(stack_trace: *StackTrace) void {
    stack_trace.instruction_addresses[stack_trace.index] = @returnAddress();
    stack_trace.index = (stack_trace.index + 1) % N;
}

成本是2次数学运算加上一些内存读写。访问的内存受限,应该在错误返回冒泡的持续时间内保持缓存。

至于代码大小成本,return语句前的一次函数调用没什么大不了的。即便如此,我也有一个计划,将对__zig_return_error的调用变成尾调用,这将使代码大小成本降至零。在没有错误返回追踪的代码中,一个return语句可以成为带有错误返回追踪的代码中的一个跳转指令。

可选类型 §

Zig 在不损害效率或可读性的前提下提供安全性的一方面是可选类型。

问号表示可选类型。您可以通过在类型前面加一个问号来将其转换为可选类型,如下所示:

optional_integer.zig
// normal integer
const normal_int: i32 = 1234;

// optional integer
const optional_int: ?i32 = 5678;

现在变量optional_int可以是i32,也可以是null

我们不谈整数,来谈谈指针。空引用是许多运行时异常的根源,甚至被指责为计算机科学最糟糕的错误

Zig 没有它们。

相反,您可以使用可选指针。这在编译时秘密地编译成一个普通指针,因为我们知道可以使用 0 作为可选类型的空值。但是编译器可以检查您的工作,并确保您不会将 null 赋值给不能为 null 的东西。

通常,没有 null 的缺点是编写代码会更冗长。但是,让我们比较一些等效的 C 代码和 Zig 代码。

任务:调用 malloc,如果结果为 null,则返回 null。

C 代码

call_malloc_in_c.c
// malloc prototype included for reference
void *malloc(size_t size);

struct Foo *do_a_thing(void) {
    char *ptr = malloc(1234);
    if (!ptr) return NULL;
    // ...
}

Zig 代码

call_malloc_from_zig.zig
// malloc prototype included for reference
extern fn malloc(size: usize) ?[*]u8;

fn doAThing() ?*Foo {
    const ptr = malloc(1234) orelse return null;
    _ = ptr; // ...
}

在这里,Zig 至少和 C 一样方便,甚至更方便。而且,“ptr”的类型是[*]u8而不是?[*]u8orelse关键字解包了可选类型,因此在函数中使用ptr的任何地方都保证它不是 null。

你可能会看到另一种检查 NULL 的形式是这样的:

checking_null_in_c.c
void do_a_thing(struct Foo *foo) {
    // do some stuff

    if (foo) {
        do_something_with_foo(foo);
    }

    // do some stuff
}

在 Zig 中,你可以实现相同的功能:

checking_null_in_zig.zig
const Foo = struct {};
fn doSomethingWithFoo(foo: *Foo) void {
    _ = foo;
}

fn doAThing(optional_foo: ?*Foo) void {
    // do some stuff

    if (optional_foo) |foo| {
        doSomethingWithFoo(foo);
    }

    // do some stuff
}

再次,值得注意的是,在 if 块内部,foo不再是可选指针,它是一个指针,不能为 null。

这样做的一个好处是,接受指针作为参数的函数可以用“nonnull”属性进行注释——在GCC中是__attribute__((nonnull))。优化器有时可以更好地决策,因为知道指针参数不能为 null。

可选类型 §

通过在类型前面加上?来创建可选类型。您可以使用编译时反射来访问可选类型的子类型。

test_optional_type.zig
const expect = @import("std").testing.expect;

test "optional type" {
    // Declare an optional and coerce from null:
    var foo: ?i32 = null;

    // Coerce from child type of an optional
    foo = 1234;

    // Use compile-time reflection to access the child type of the optional:
    try comptime expect(@typeInfo(@TypeOf(foo)).optional.child == i32);
}
Shell
$ zig test test_optional_type.zig
1/1 test_optional_type.test.optional type...OK
All 1 tests passed.

null §

就像undefined一样,null有自己的类型,唯一的使用方式是将其强制转换为不同的类型。

null.zig
const optional_value: ?i32 = null;

可选指针 §

可选指针保证与指针大小相同。可选的null保证为地址0。

test_optional_pointer.zig
const expect = @import("std").testing.expect;

test "optional pointers" {
    // Pointers cannot be null. If you want a null pointer, use the optional
    // prefix `?` to make the pointer type optional.
    var ptr: ?*i32 = null;

    var x: i32 = 1;
    ptr = &x;

    try expect(ptr.?.* == 1);

    // Optional pointers are the same size as normal pointers, because pointer
    // value 0 is used as the null value.
    try expect(@sizeOf(?*i32) == @sizeOf(*i32));
}
Shell
$ zig test test_optional_pointer.zig
1/1 test_optional_pointer.test.optional pointers...OK
All 1 tests passed.

另请参阅

类型转换 §

类型转换将一个类型的值转换为另一个类型。Zig 对于已知完全安全和明确的转换使用类型强制转换,对于不希望意外发生的转换使用显式类型转换。还有第三种类型转换称为对等类型解析,用于在给定多个操作数类型时必须确定结果类型的情况。

类型强制转换 §

当预期一种类型但提供了不同类型时,会发生类型强制转换。

test_type_coercion.zig
test "type coercion - variable declaration" {
    const a: u8 = 1;
    const b: u16 = a;
    _ = b;
}

test "type coercion - function call" {
    const a: u8 = 1;
    foo(a);
}

fn foo(b: u16) void {
    _ = b;
}

test "type coercion - @as builtin" {
    const a: u8 = 1;
    const b = @as(u16, a);
    _ = b;
}
Shell
$ zig test test_type_coercion.zig
1/3 test_type_coercion.test.type coercion - variable declaration...OK
2/3 test_type_coercion.test.type coercion - function call...OK
3/3 test_type_coercion.test.type coercion - @as builtin...OK
All 3 tests passed.

类型强制转换只允许在从一种类型到另一种类型的转换完全明确且转换保证安全的情况下进行。有一个例外是C 指针

类型强制转换:更严格的限定 §

运行时具有相同表示的值可以强制转换以增加限定符的严格性,无论限定符嵌套多深。

  • const - 非 const 到 const 允许
  • volatile - 非 volatile 到 volatile 允许
  • align - 大对齐到小对齐允许
  • 错误集到超集允许

这些转换在运行时是空操作,因为值表示没有改变。

test_no_op_casts.zig
test "type coercion - const qualification" {
    var a: i32 = 1;
    const b: *i32 = &a;
    foo(b);
}

fn foo(_: *const i32) void {}
Shell
$ zig test test_no_op_casts.zig
1/1 test_no_op_casts.test.type coercion - const qualification...OK
All 1 tests passed.

此外,指针可以强制转换为 const 可选指针。

test_pointer_coerce_const_optional.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;

test "cast *[1][*:0]const u8 to []const ?[*:0]const u8" {
    const window_name = [1][*:0]const u8{"window name"};
    const x: []const ?[*:0]const u8 = &window_name;
    try expect(mem.eql(u8, mem.span(x[0].?), "window name"));
}
Shell
$ zig test test_pointer_coerce_const_optional.zig
1/1 test_pointer_coerce_const_optional.test.cast *[1][*:0]const u8 to []const ?[*:0]const u8...OK
All 1 tests passed.

类型强制转换:整数和浮点数拓宽 §

整数会强制转换为可以表示旧类型所有值的整数类型,同样,浮点数会强制转换为可以表示旧类型所有值的浮点类型。

test_integer_widening.zig
const std = @import("std");
const builtin = @import("builtin");
const expect = std.testing.expect;
const mem = std.mem;

test "integer widening" {
    const a: u8 = 250;
    const b: u16 = a;
    const c: u32 = b;
    const d: u64 = c;
    const e: u64 = d;
    const f: u128 = e;
    try expect(f == a);
}

test "implicit unsigned integer to signed integer" {
    const a: u8 = 250;
    const b: i16 = a;
    try expect(b == 250);
}

test "float widening" {
    const a: f16 = 12.34;
    const b: f32 = a;
    const c: f64 = b;
    const d: f128 = c;
    try expect(d == a);
}
Shell
$ zig test test_integer_widening.zig
1/3 test_integer_widening.test.integer widening...OK
2/3 test_integer_widening.test.implicit unsigned integer to signed integer...OK
3/3 test_integer_widening.test.float widening...OK
All 3 tests passed.

类型强制转换:浮点数到整数 §

发生编译器错误是合适的,因为这个模糊的表达式给编译器留下了两种关于强制转换的选择。

  • 54.0强制转换为comptime_int,结果为@as(comptime_int, 10),然后强制转换为@as(f32, 10)
  • 5强制转换为comptime_float,结果为@as(comptime_float, 10.8),然后强制转换为@as(f32, 10.8)
test_ambiguous_coercion.zig
// Compile time coercion of float to int
test "implicit cast to comptime_int" {
    const f: f32 = 54.0 / 5;
    _ = f;
}
Shell
$ zig test test_ambiguous_coercion.zig
/home/andy/dev/zig/doc/langref/test_ambiguous_coercion.zig:3:25: error: ambiguous coercion of division operands 'comptime_float' and 'comptime_int'; non-zero remainder '4'
    const f: f32 = 54.0 / 5;
                   ~~~~~^~~

类型强制转换:切片、数组和指针 §

test_coerce_slices_arrays_and_pointers.zig
const std = @import("std");
const expect = std.testing.expect;

// You can assign constant pointers to arrays to a slice with
// const modifier on the element type. Useful in particular for
// String literals.
test "*const [N]T to []const T" {
    const x1: []const u8 = "hello";
    const x2: []const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
    try expect(std.mem.eql(u8, x1, x2));

    const y: []const f32 = &[2]f32{ 1.2, 3.4 };
    try expect(y[0] == 1.2);
}

// Likewise, it works when the destination type is an error union.
test "*const [N]T to E![]const T" {
    const x1: anyerror![]const u8 = "hello";
    const x2: anyerror![]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
    try expect(std.mem.eql(u8, try x1, try x2));

    const y: anyerror![]const f32 = &[2]f32{ 1.2, 3.4 };
    try expect((try y)[0] == 1.2);
}

// Likewise, it works when the destination type is an optional.
test "*const [N]T to ?[]const T" {
    const x1: ?[]const u8 = "hello";
    const x2: ?[]const u8 = &[5]u8{ 'h', 'e', 'l', 'l', 111 };
    try expect(std.mem.eql(u8, x1.?, x2.?));

    const y: ?[]const f32 = &[2]f32{ 1.2, 3.4 };
    try expect(y.?[0] == 1.2);
}

// In this cast, the array length becomes the slice length.
test "*[N]T to []T" {
    var buf: [5]u8 = "hello".*;
    const x: []u8 = &buf;
    try expect(std.mem.eql(u8, x, "hello"));

    const buf2 = [2]f32{ 1.2, 3.4 };
    const x2: []const f32 = &buf2;
    try expect(std.mem.eql(f32, x2, &[2]f32{ 1.2, 3.4 }));
}

// Single-item pointers to arrays can be coerced to many-item pointers.
test "*[N]T to [*]T" {
    var buf: [5]u8 = "hello".*;
    const x: [*]u8 = &buf;
    try expect(x[4] == 'o');
    // x[5] would be an uncaught out of bounds pointer dereference!
}

// Likewise, it works when the destination type is an optional.
test "*[N]T to ?[*]T" {
    var buf: [5]u8 = "hello".*;
    const x: ?[*]u8 = &buf;
    try expect(x.?[4] == 'o');
}

// Single-item pointers can be cast to len-1 single-item arrays.
test "*T to *[1]T" {
    var x: i32 = 1234;
    const y: *[1]i32 = &x;
    const z: [*]i32 = y;
    try expect(z[0] == 1234);
}
Shell
$ zig test test_coerce_slices_arrays_and_pointers.zig
1/7 test_coerce_slices_arrays_and_pointers.test.*const [N]T to []const T...OK
2/7 test_coerce_slices_arrays_and_pointers.test.*const [N]T to E![]const T...OK
3/7 test_coerce_slices_arrays_and_pointers.test.*const [N]T to ?[]const T...OK
4/7 test_coerce_slices_arrays_and_pointers.test.*[N]T to []T...OK
5/7 test_coerce_slices_arrays_and_pointers.test.*[N]T to [*]T...OK
6/7 test_coerce_slices_arrays_and_pointers.test.*[N]T to ?[*]T...OK
7/7 test_coerce_slices_arrays_and_pointers.test.*T to *[1]T...OK
All 7 tests passed.

另请参阅

类型强制转换:可选类型 §

可选类型的有效载荷类型以及null都会强制转换为可选类型。

test_coerce_optionals.zig
const std = @import("std");
const expect = std.testing.expect;

test "coerce to optionals" {
    const x: ?i32 = 1234;
    const y: ?i32 = null;

    try expect(x.? == 1234);
    try expect(y == null);
}
Shell
$ zig test test_coerce_optionals.zig
1/1 test_coerce_optionals.test.coerce to optionals...OK
All 1 tests passed.

可选类型也可以嵌套在错误联合体类型中:

test_coerce_optional_wrapped_error_union.zig
const std = @import("std");
const expect = std.testing.expect;

test "coerce to optionals wrapped in error union" {
    const x: anyerror!?i32 = 1234;
    const y: anyerror!?i32 = null;

    try expect((try x).? == 1234);
    try expect((try y) == null);
}
Shell
$ zig test test_coerce_optional_wrapped_error_union.zig
1/1 test_coerce_optional_wrapped_error_union.test.coerce to optionals wrapped in error union...OK
All 1 tests passed.

类型强制转换:错误联合体 §

错误联合体类型的有效载荷类型以及错误集类型会强制转换为错误联合体类型。

test_coerce_to_error_union.zig
const std = @import("std");
const expect = std.testing.expect;

test "coercion to error unions" {
    const x: anyerror!i32 = 1234;
    const y: anyerror!i32 = error.Failure;

    try expect((try x) == 1234);
    try std.testing.expectError(error.Failure, y);
}
Shell
$ zig test test_coerce_to_error_union.zig
1/1 test_coerce_to_error_union.test.coercion to error unions...OK
All 1 tests passed.

类型强制转换:编译时已知数字 §

当数字在编译时已知可以在目标类型中表示时,可以进行强制转换。

test_coerce_large_to_small.zig
const std = @import("std");
const expect = std.testing.expect;

test "coercing large integer type to smaller one when value is comptime-known to fit" {
    const x: u64 = 255;
    const y: u8 = x;
    try expect(y == 255);
}
Shell
$ zig test test_coerce_large_to_small.zig
1/1 test_coerce_large_to_small.test.coercing large integer type to smaller one when value is comptime-known to fit...OK
All 1 tests passed.

类型强制转换:联合体和枚举 §

带标签的联合体可以强制转换为枚举,而枚举可以在编译时已知为联合体中只有一个可能值(例如void)的字段时强制转换为带标签的联合体。

test_coerce_unions_enums.zig
const std = @import("std");
const expect = std.testing.expect;

const E = enum {
    one,
    two,
    three,
};

const U = union(E) {
    one: i32,
    two: f32,
    three,
};

const U2 = union(enum) {
    a: void,
    b: f32,

    fn tag(self: U2) usize {
        switch (self) {
            .a => return 1,
            .b => return 2,
        }
    }
};

test "coercion between unions and enums" {
    const u = U{ .two = 12.34 };
    const e: E = u; // coerce union to enum
    try expect(e == E.two);

    const three = E.three;
    const u_2: U = three; // coerce enum to union
    try expect(u_2 == E.three);

    const u_3: U = .three; // coerce enum literal to union
    try expect(u_3 == E.three);

    const u_4: U2 = .a; // coerce enum literal to union with inferred enum tag type.
    try expect(u_4.tag() == 1);

    // The following example is invalid.
    // error: coercion from enum '@TypeOf(.enum_literal)' to union 'test_coerce_unions_enum.U2' must initialize 'f32' field 'b'
    //var u_5: U2 = .b;
    //try expect(u_5.tag() == 2);
}
Shell
$ zig test test_coerce_unions_enums.zig
1/1 test_coerce_unions_enums.test.coercion between unions and enums...OK
All 1 tests passed.

另请参阅

类型强制转换:undefined §

undefined可以强制转换为任何类型。

类型强制转换:元组到数组 §

元组可以强制转换为数组,如果所有字段都具有相同的类型。

test_coerce_tuples_arrays.zig
const std = @import("std");
const expect = std.testing.expect;

const Tuple = struct { u8, u8 };
test "coercion from homogeneous tuple to array" {
    const tuple: Tuple = .{ 5, 6 };
    const array: [2]u8 = tuple;
    _ = array;
}
Shell
$ zig test test_coerce_tuples_arrays.zig
1/1 test_coerce_tuples_arrays.test.coercion from homogeneous tuple to array...OK
All 1 tests passed.

显式类型转换 §

显式类型转换通过内置函数执行。有些显式类型转换是安全的;有些则不是。有些显式类型转换执行语言级断言;有些则不。有些显式类型转换在运行时是空操作;有些则不是。

对等类型解析 §

对等类型解析发生在以下位置:

这种类型解析选择一种所有对等类型都可以强制转换为的类型。以下是一些例子:

test_peer_type_resolution.zig
const std = @import("std");
const expect = std.testing.expect;
const mem = std.mem;

test "peer resolve int widening" {
    const a: i8 = 12;
    const b: i16 = 34;
    const c = a + b;
    try expect(c == 46);
    try expect(@TypeOf(c) == i16);
}

test "peer resolve arrays of different size to const slice" {
    try expect(mem.eql(u8, boolToStr(true), "true"));
    try expect(mem.eql(u8, boolToStr(false), "false"));
    try comptime expect(mem.eql(u8, boolToStr(true), "true"));
    try comptime expect(mem.eql(u8, boolToStr(false), "false"));
}
fn boolToStr(b: bool) []const u8 {
    return if (b) "true" else "false";
}

test "peer resolve array and const slice" {
    try testPeerResolveArrayConstSlice(true);
    try comptime testPeerResolveArrayConstSlice(true);
}
fn testPeerResolveArrayConstSlice(b: bool) !void {
    const value1 = if (b) "aoeu" else @as([]const u8, "zz");
    const value2 = if (b) @as([]const u8, "zz") else "aoeu";
    try expect(mem.eql(u8, value1, "aoeu"));
    try expect(mem.eql(u8, value2, "zz"));
}

test "peer type resolution: ?T and T" {
    try expect(peerTypeTAndOptionalT(true, false).? == 0);
    try expect(peerTypeTAndOptionalT(false, false).? == 3);
    comptime {
        try expect(peerTypeTAndOptionalT(true, false).? == 0);
        try expect(peerTypeTAndOptionalT(false, false).? == 3);
    }
}
fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
    if (c) {
        return if (b) null else @as(usize, 0);
    }

    return @as(usize, 3);
}

test "peer type resolution: *[0]u8 and []const u8" {
    try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
    try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
    comptime {
        try expect(peerTypeEmptyArrayAndSlice(true, "hi").len == 0);
        try expect(peerTypeEmptyArrayAndSlice(false, "hi").len == 1);
    }
}
fn peerTypeEmptyArrayAndSlice(a: bool, slice: []const u8) []const u8 {
    if (a) {
        return &[_]u8{};
    }

    return slice[0..1];
}
test "peer type resolution: *[0]u8, []const u8, and anyerror![]u8" {
    {
        var data = "hi".*;
        const slice = data[0..];
        try expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
        try expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
    }
    comptime {
        var data = "hi".*;
        const slice = data[0..];
        try expect((try peerTypeEmptyArrayAndSliceAndError(true, slice)).len == 0);
        try expect((try peerTypeEmptyArrayAndSliceAndError(false, slice)).len == 1);
    }
}
fn peerTypeEmptyArrayAndSliceAndError(a: bool, slice: []u8) anyerror![]u8 {
    if (a) {
        return &[_]u8{};
    }

    return slice[0..1];
}

test "peer type resolution: *const T and ?*T" {
    const a: *const usize = @ptrFromInt(0x123456780);
    const b: ?*usize = @ptrFromInt(0x123456780);
    try expect(a == b);
    try expect(b == a);
}

test "peer type resolution: error union switch" {
    // The non-error and error cases are only peers if the error case is just a switch expression;
    // the pattern `if (x) {...} else |err| blk: { switch (err) {...} }` does not consider the
    // non-error and error case to be peers.
    var a: error{ A, B, C }!u32 = 0;
    _ = &a;
    const b = if (a) |x|
        x + 3
    else |err| switch (err) {
        error.A => 0,
        error.B => 1,
        error.C => null,
    };
    try expect(@TypeOf(b) == ?u32);

    // The non-error and error cases are only peers if the error case is just a switch expression;
    // the pattern `x catch |err| blk: { switch (err) {...} }` does not consider the unwrapped `x`
    // and error case to be peers.
    const c = a catch |err| switch (err) {
        error.A => 0,
        error.B => 1,
        error.C => null,
    };
    try expect(@TypeOf(c) == ?u32);
}
Shell
$ zig test test_peer_type_resolution.zig
1/8 test_peer_type_resolution.test.peer resolve int widening...OK
2/8 test_peer_type_resolution.test.peer resolve arrays of different size to const slice...OK
3/8 test_peer_type_resolution.test.peer resolve array and const slice...OK
4/8 test_peer_type_resolution.test.peer type resolution: ?T and T...OK
5/8 test_peer_type_resolution.test.peer type resolution: *[0]u8 and []const u8...OK
6/8 test_peer_type_resolution.test.peer type resolution: *[0]u8, []const u8, and anyerror![]u8...OK
7/8 test_peer_type_resolution.test.peer type resolution: *const T and ?*T...OK
8/8 test_peer_type_resolution.test.peer type resolution: error union switch...OK
All 8 tests passed.

零位类型 §

对于某些类型,@sizeOf 为 0:

这些类型只能有一个可能的值,因此需要 0 位来表示。使用这些类型的代码不会包含在最终生成的代码中。

zero_bit_types.zig
export fn entry() void {
    var x: void = {};
    var y: void = {};
    x = y;
    y = x;
}

当这转换为机器码时,即使在Debug模式下,entry的主体中也不会生成任何代码。例如,在 x86_64 上:

0000000000000010 <entry>:
  10:	55                   	push   %rbp
  11:	48 89 e5             	mov    %rsp,%rbp
  14:	5d                   	pop    %rbp
  15:	c3                   	retq   

这些汇编指令不包含任何与 void 值相关的代码——它们只执行函数调用序言和尾声。

void §

void对于实例化泛型类型很有用。例如,给定一个Map(Key, Value),可以将void作为Value类型传入,使其成为一个Set

test_void_in_hashmap.zig
const std = @import("std");
const expect = std.testing.expect;

test "turn HashMap into a set with void" {
    var map = std.AutoHashMap(i32, void).init(std.testing.allocator);
    defer map.deinit();

    try map.put(1, {});
    try map.put(2, {});

    try expect(map.contains(2));
    try expect(!map.contains(3));

    _ = map.remove(2);
    try expect(!map.contains(2));
}
Shell
$ zig test test_void_in_hashmap.zig
1/1 test_void_in_hashmap.test.turn HashMap into a set with void...OK
All 1 tests passed.

请注意,这与对哈希映射值使用虚拟值不同。通过使用void作为值类型,哈希映射条目类型没有值字段,因此哈希映射占用的空间更小。此外,所有处理值存储和加载的代码都会被删除,如上所示。

voidanyopaque不同。void的已知大小为0字节,而anyopaque的大小未知,但非零。

void类型的表达式是唯一可以忽略其值的表达式。例如,忽略非void表达式会导致编译错误。

test_expression_ignored.zig
test "ignoring expression value" {
    foo();
}

fn foo() i32 {
    return 1234;
}
Shell
$ zig test test_expression_ignored.zig
/home/andy/dev/zig/doc/langref/test_expression_ignored.zig:2:8: error: value of type 'i32' ignored
    foo();
    ~~~^~
/home/andy/dev/zig/doc/langref/test_expression_ignored.zig:2:8: note: all non-void values must be used
/home/andy/dev/zig/doc/langref/test_expression_ignored.zig:2:8: note: to discard the value, assign it to '_'

然而,如果表达式的类型是void,则不会有错误。表达式结果可以通过赋值给_来显式忽略。

test_void_ignored.zig
test "void is ignored" {
    returnsVoid();
}

test "explicitly ignoring expression value" {
    _ = foo();
}

fn returnsVoid() void {}

fn foo() i32 {
    return 1234;
}
Shell
$ zig test test_void_ignored.zig
1/2 test_void_ignored.test.void is ignored...OK
2/2 test_void_ignored.test.explicitly ignoring expression value...OK
All 2 tests passed.

结果位置语义 §

在编译期间,每个 Zig 表达式和子表达式都被分配了可选的结果位置信息。此信息规定了表达式应具有的类型(其结果类型),以及结果值应放置在内存中的位置(其结果位置)。该信息是可选的,因为并非每个表达式都具有此信息:例如,赋值给_不提供有关表达式类型的任何信息,也不提供放置值的具体内存位置。

举个例子,考虑语句const x: u32 = 42;。这里的类型注解为初始化表达式42提供了u32的结果类型,指示编译器将此整数(最初类型为comptime_int)强制转换为此类型。我们很快会看到更多示例。

这不是实现细节:上述逻辑已编入 Zig 语言规范,是该语言中类型推断的主要机制。此系统统称为“结果位置语义”。

结果类型 §

结果类型在可能的情况下通过表达式递归传播。例如,如果表达式&e的结果类型是*u32,那么e将被赋予u32的结果类型,允许语言在获取引用之前执行这种强制转换。

结果类型机制被强制转换内置函数(如@intCast)利用。这些内置函数不是将要强制转换的类型作为参数,而是使用它们的结果类型来确定此信息。结果类型通常从上下文中得知;如果不知道,可以使用@as内置函数显式提供结果类型。

我们可以将简单表达式的每个组成部分的结果类型分解如下:

result_type_propagation.zig
const expectEqual = @import("std").testing.expectEqual;
test "result type propagates through struct initializer" {
    const S = struct { x: u32 };
    const val: u64 = 123;
    const s: S = .{ .x = @intCast(val) };
    // .{ .x = @intCast(val) }   has result type `S` due to the type annotation
    //         @intCast(val)     has result type `u32` due to the type of the field `S.x`
    //                  val      has no result type, as it is permitted to be any integer type
    try expectEqual(@as(u32, 123), s.x);
}
Shell
$ zig test result_type_propagation.zig
1/1 result_type_propagation.test.result type propagates through struct initializer...OK
All 1 tests passed.

此结果类型信息对于上述的强制转换内置函数很有用,也可以避免构建预强制转换值,并在某些情况下避免显式类型强制转换的需要。下表详细说明了一些常见表达式如何传播结果类型,其中xy是任意子表达式。

表达式 父结果类型 子表达式结果类型
const val: T = x - xT
var val: T = x - xT
val = x - x@TypeOf(val)
@as(T, x) - xT
&x *T xT
&x []T xT的某个数组
f(x) - x具有f的第一个参数的类型
.{x} T x@FieldType(T, "0")
.{ .a = x } T x@FieldType(T, "a")
T{x} - x@FieldType(T, "0")
T{ .a = x } - x@FieldType(T, "a")
@Type(x) - xstd.builtin.Type
@typeInfo(x) - xtype
x << y - ystd.math.Log2IntCeil(@TypeOf(x))

结果位置 §

除了结果类型信息之外,每个表达式还可以选择性地分配一个结果位置:一个指向其值必须直接写入的指针。此系统可用于在初始化数据结构时防止中间复制,这对于必须具有固定内存地址(“固定”类型)的类型可能很重要。

编译简单赋值表达式x = e时,许多语言会在栈上创建临时值e,然后将其赋值给x,在此过程中可能会执行类型强制转换。Zig 以不同的方式处理这个问题。表达式e被赋予与x类型匹配的结果类型,以及&x的结果位置。对于许多e的语法形式,这没有实际影响。然而,当处理更复杂的语法形式时,它可能具有重要的语义效应。

例如,如果表达式.{ .a = x, .b = y }的结果位置是ptr,那么x将被赋予结果位置&ptr.ay将被赋予结果位置&ptr.b。没有这个系统,这个表达式将完全在栈上构造一个临时结构体值,然后才将其复制到目标地址。本质上,Zig 将赋值foo = .{ .a = x, .b = y }解糖为两个语句foo.a = x; foo.b = y;

这在分配聚合值时有时很重要,其中初始化表达式依赖于聚合的先前值。最简单的演示方法是尝试交换结构体或数组的字段——以下逻辑看起来是正确的,但实际上不是:

result_location_interfering_with_swap.zig
const expect = @import("std").testing.expect;
test "attempt to swap array elements with array initializer" {
    var arr: [2]u32 = .{ 1, 2 };
    arr = .{ arr[1], arr[0] };
    // The previous line is equivalent to the following two lines:
    //   arr[0] = arr[1];
    //   arr[1] = arr[0];
    // So this fails!
    try expect(arr[0] == 2); // succeeds
    try expect(arr[1] == 1); // fails
}
Shell
$ zig test result_location_interfering_with_swap.zig
1/1 result_location_interfering_with_swap.test.attempt to swap array elements with array initializer...FAIL (TestUnexpectedResult)
/home/andy/dev/zig/lib/std/testing.zig:580:14: 0x10488ef in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/home/andy/dev/zig/doc/langref/result_location_interfering_with_swap.zig:10:5: 0x10489d5 in test.attempt to swap array elements with array initializer (test)
    try expect(arr[1] == 1); // fails
    ^
0 passed; 0 skipped; 1 failed.
error: the following test command failed with exit code 1:
/home/andy/dev/zig/.zig-cache/o/ad052551079782d9997770925a86a745/test --seed=0x7bedf42

下表详细说明了一些常见表达式如何传播结果位置,其中xy是任意子表达式。请注意,有些表达式无法为子表达式提供有意义的结果位置,即使它们本身具有结果位置。

表达式 结果位置 子表达式结果位置
const val: T = x - x的结果位置是&val
var val: T = x - x的结果位置是&val
val = x - x的结果位置是&val
@as(T, x) ptr x没有结果位置
&x ptr x没有结果位置
f(x) ptr x没有结果位置
.{x} ptr x的结果位置是&ptr[0]
.{ .a = x } ptr x的结果位置是&ptr.a
T{x} ptr x没有结果位置(类型化初始化器不传播结果位置)
T{ .a = x } ptr x没有结果位置(类型化初始化器不传播结果位置)
@Type(x) ptr x没有结果位置
@typeInfo(x) ptr x没有结果位置
x << y ptr xy没有结果位置

usingnamespace §

usingnamespace是一个声明,它将操作数的所有公共声明(操作数必须是结构体联合体枚举不透明类型)混合到当前命名空间中。

test_usingnamespace.zig
test "using std namespace" {
    const S = struct {
        usingnamespace @import("std");
    };
    try S.testing.expect(true);
}
Shell
$ zig test test_usingnamespace.zig
1/1 test_usingnamespace.test.using std namespace...OK
All 1 tests passed.

usingnamespace在组织文件或包的公共 API 时有一个重要的用例。例如,可能有一个包含所有C 导入c.zig文件:

c.zig
pub usingnamespace @cImport({
    @cInclude("epoxy/gl.h");
    @cInclude("GLFW/glfw3.h");
    @cDefine("STBI_ONLY_PNG", "");
    @cDefine("STBI_NO_STDIO", "");
    @cInclude("stb_image.h");
});

上述示例演示了使用pub限定usingnamespace,这会额外使导入的声明成为pub。这可以用于转发声明,从而精确控制给定文件暴露的声明。

comptime §

Zig 重视表达式在编译时是否已知这一概念。这个概念在几个不同的地方被使用,这些构建块用于保持语言小巧、可读且强大。

引入编译时概念 §

编译期参数 §

编译时参数是 Zig 实现泛型的方式。它是编译时鸭子类型。

compile-time_duck_typing.zig
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
fn gimmeTheBiggerFloat(a: f32, b: f32) f32 {
    return max(f32, a, b);
}
fn gimmeTheBiggerInteger(a: u64, b: u64) u64 {
    return max(u64, a, b);
}

在 Zig 中,类型是一等公民。它们可以赋值给变量,作为参数传递给函数,并从函数返回。然而,它们只能用于编译时已知的表达式,这就是为什么上面代码片段中的参数T必须标记为comptime

comptime参数意味着:

  • 在调用点,该值必须在编译时已知,否则会产生编译错误。
  • 在函数定义中,该值在编译时已知。

例如,如果我们在上面的代码片段中引入另一个函数:

test_unresolved_comptime_value.zig
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
test "try to pass a runtime type" {
    foo(false);
}
fn foo(condition: bool) void {
    const result = max(if (condition) f32 else u64, 1234, 5678);
    _ = result;
}
Shell
$ zig test test_unresolved_comptime_value.zig
/home/andy/dev/zig/doc/langref/test_unresolved_comptime_value.zig:8:28: error: unable to resolve comptime value
    const result = max(if (condition) f32 else u64, 1234, 5678);
                           ^~~~~~~~~
/home/andy/dev/zig/doc/langref/test_unresolved_comptime_value.zig:8:24: note: argument to comptime parameter must be comptime-known
    const result = max(if (condition) f32 else u64, 1234, 5678);
                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_unresolved_comptime_value.zig:1:8: note: parameter declared comptime here
fn max(comptime T: type, a: T, b: T) T {
       ^~~~~~~~
referenced by:
    test.try to pass a runtime type: /home/andy/dev/zig/doc/langref/test_unresolved_comptime_value.zig:5:8

这是一个错误,因为程序员试图将一个只在运行时已知的值传递给一个期望在编译时已知值的函数。

另一种产生错误的方式是,如果我们传入的类型在函数分析时违反了类型检查器。这就是编译时鸭子类型的含义。

例如:

test_comptime_mismatched_type.zig
fn max(comptime T: type, a: T, b: T) T {
    return if (a > b) a else b;
}
test "try to compare bools" {
    _ = max(bool, true, false);
}
Shell
$ zig test test_comptime_mismatched_type.zig
/home/andy/dev/zig/doc/langref/test_comptime_mismatched_type.zig:2:18: error: operator > not allowed for type 'bool'
    return if (a > b) a else b;
               ~~^~~
referenced by:
    test.try to compare bools: /home/andy/dev/zig/doc/langref/test_comptime_mismatched_type.zig:5:12

另一方面,在带有comptime参数的函数定义内部,该值在编译时是已知的。这意味着如果我们愿意,我们实际上可以使其适用于布尔类型。

test_comptime_max_with_bool.zig
fn max(comptime T: type, a: T, b: T) T {
    if (T == bool) {
        return a or b;
    } else if (a > b) {
        return a;
    } else {
        return b;
    }
}
test "try to compare bools" {
    try @import("std").testing.expect(max(bool, false, true) == true);
}
Shell
$ zig test test_comptime_max_with_bool.zig
1/1 test_comptime_max_with_bool.test.try to compare bools...OK
All 1 tests passed.

这之所以奏效,是因为当条件在编译时已知时,Zig 会隐式地内联if表达式,并且编译器保证会跳过对未执行分支的分析。

这意味着在这种情况下,为max实际生成的函数看起来像这样:

compiler_generated_function.zig
fn max(a: bool, b: bool) bool {
    {
        return a or b;
    }
}

所有处理编译时已知值的代码都被消除了,我们只剩下完成任务所需的运行时代码。

对于switch表达式,其工作方式相同——当目标表达式在编译时已知时,它们会隐式内联。

编译期变量 §

在 Zig 中,程序员可以将变量标记为comptime。这向编译器保证,变量的每次加载和存储都在编译时执行。任何违反此规定的行为都会导致编译错误。

这与我们可以inline循环的事实相结合,使我们能够编写一个部分在编译时评估,部分在运行时评估的函数。

例如:

test_comptime_evaluation.zig
const expect = @import("std").testing.expect;

const CmdFn = struct {
    name: []const u8,
    func: fn (i32) i32,
};

const cmd_fns = [_]CmdFn{
    CmdFn{ .name = "one", .func = one },
    CmdFn{ .name = "two", .func = two },
    CmdFn{ .name = "three", .func = three },
};
fn one(value: i32) i32 {
    return value + 1;
}
fn two(value: i32) i32 {
    return value + 2;
}
fn three(value: i32) i32 {
    return value + 3;
}

fn performFn(comptime prefix_char: u8, start_value: i32) i32 {
    var result: i32 = start_value;
    comptime var i = 0;
    inline while (i < cmd_fns.len) : (i += 1) {
        if (cmd_fns[i].name[0] == prefix_char) {
            result = cmd_fns[i].func(result);
        }
    }
    return result;
}

test "perform fn" {
    try expect(performFn('t', 1) == 6);
    try expect(performFn('o', 0) == 1);
    try expect(performFn('w', 99) == 99);
}
Shell
$ zig test test_comptime_evaluation.zig
1/1 test_comptime_evaluation.test.perform fn...OK
All 1 tests passed.

这个例子有点做作,因为编译时评估部分是不必要的;如果所有操作都在运行时完成,这段代码也会正常工作。但它确实会生成不同的代码。在这个例子中,函数performFn会为prefix_char的不同值生成三次:

performFn_1
// From the line:
// expect(performFn('t', 1) == 6);
fn performFn(start_value: i32) i32 {
    var result: i32 = start_value;
    result = two(result);
    result = three(result);
    return result;
}
performFn_2
// From the line:
// expect(performFn('o', 0) == 1);
fn performFn(start_value: i32) i32 {
    var result: i32 = start_value;
    result = one(result);
    return result;
}
performFn_3
// From the line:
// expect(performFn('w', 99) == 99);
fn performFn(start_value: i32) i32 {
    var result: i32 = start_value;
    _ = &result;
    return result;
}

请注意,即使在调试构建中也会发生这种情况。这不是一种编写更优化代码的方式,但它是一种确保应该在编译时发生的事情确实在编译时发生的方式。这可以捕获更多错误,并允许在其他语言中需要使用宏、生成代码或预处理器才能实现的那种表达能力。

编译期表达式 §

在 Zig 中,一个给定表达式是编译时已知还是运行时已知很重要。程序员可以使用comptime表达式来保证该表达式将在编译时求值。如果无法做到这一点,编译器将发出错误。例如:

test_comptime_call_extern_function.zig
extern fn exit() noreturn;

test "foo" {
    comptime {
        exit();
    }
}
Shell
$ zig test test_comptime_call_extern_function.zig
/home/andy/dev/zig/doc/langref/test_comptime_call_extern_function.zig:5:13: error: comptime call of extern function
        exit();
        ~~~~^~
/home/andy/dev/zig/doc/langref/test_comptime_call_extern_function.zig:4:5: note: 'comptime' keyword forces comptime evaluation
    comptime {
    ^~~~~~~~

程序在编译时调用exit()(或任何其他外部函数)是没有意义的,所以这是一个编译错误。然而,一个comptime表达式远不止有时会导致编译错误。

comptime表达式中:

  • 所有变量都是comptime变量。
  • 所有ifwhileforswitch表达式都在编译时求值,如果不可能则会发出编译错误。
  • 所有returntry表达式都是无效的(除非函数本身是在编译时调用的)。
  • 所有具有运行时副作用或依赖运行时值的代码都会发出编译错误。
  • 所有函数调用都会导致编译器在编译时解释该函数,如果函数尝试执行具有全局运行时副作用的操作,则会发出编译错误。

这意味着程序员可以创建一个既在编译时又在运行时调用的函数,而无需对函数进行任何修改。

让我们看一个例子:

test_fibonacci_recursion.zig
const expect = @import("std").testing.expect;

fn fibonacci(index: u32) u32 {
    if (index < 2) return index;
    return fibonacci(index - 1) + fibonacci(index - 2);
}

test "fibonacci" {
    // test fibonacci at run-time
    try expect(fibonacci(7) == 13);

    // test fibonacci at compile-time
    try comptime expect(fibonacci(7) == 13);
}
Shell
$ zig test test_fibonacci_recursion.zig
1/1 test_fibonacci_recursion.test.fibonacci...OK
All 1 tests passed.

想象一下,如果我们忘记了递归函数的基例,并尝试运行测试:

test_fibonacci_comptime_overflow.zig
const expect = @import("std").testing.expect;

fn fibonacci(index: u32) u32 {
    //if (index < 2) return index;
    return fibonacci(index - 1) + fibonacci(index - 2);
}

test "fibonacci" {
    try comptime expect(fibonacci(7) == 13);
}
Shell
$ zig test test_fibonacci_comptime_overflow.zig
/home/andy/dev/zig/doc/langref/test_fibonacci_comptime_overflow.zig:5:28: error: overflow of integer type 'u32' with value '-1'
    return fibonacci(index - 1) + fibonacci(index - 2);
                     ~~~~~~^~~
/home/andy/dev/zig/doc/langref/test_fibonacci_comptime_overflow.zig:5:21: note: called from here (7 times)
    return fibonacci(index - 1) + fibonacci(index - 2);
           ~~~~~~~~~^~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_fibonacci_comptime_overflow.zig:9:34: note: called from here
    try comptime expect(fibonacci(7) == 13);
                        ~~~~~~~~~^~~

编译器会生成一个错误,它是尝试在编译时评估函数时的堆栈追踪。

幸运的是,我们使用了无符号整数,所以当我们试图从0中减去1时,它触发了非法行为,如果编译器知道它发生了,这总是编译错误。但如果我们使用有符号整数会发生什么呢?

fibonacci_comptime_infinite_recursion.zig
const assert = @import("std").debug.assert;

fn fibonacci(index: i32) i32 {
    //if (index < 2) return index;
    return fibonacci(index - 1) + fibonacci(index - 2);
}

test "fibonacci" {
    try comptime assert(fibonacci(7) == 13);
}

编译器应该注意到在编译时评估此函数花费了超过 1000 个分支,因此会发出错误并放弃。如果程序员想要增加编译时计算的预算,他们可以使用内置函数@setEvalBranchQuota将默认值 1000 更改为其他值。

然而,编译器存在一个设计缺陷,导致其在此处发生堆栈溢出而不是正常的行为。对此我深感抱歉。我希望在下次发布之前解决这个问题。

如果我们修复了基例,但在expect行中放入了错误的值会怎样?

test_fibonacci_comptime_unreachable.zig
const assert = @import("std").debug.assert;

fn fibonacci(index: i32) i32 {
    if (index < 2) return index;
    return fibonacci(index - 1) + fibonacci(index - 2);
}

test "fibonacci" {
    try comptime assert(fibonacci(7) == 99999);
}
Shell
$ zig test test_fibonacci_comptime_unreachable.zig
/home/andy/dev/zig/lib/std/debug.zig:550:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_fibonacci_comptime_unreachable.zig:9:24: note: called from here
    try comptime assert(fibonacci(7) == 99999);
                 ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~

容器级别(任何函数之外),所有表达式都隐式地是comptime表达式。这意味着我们可以使用函数来初始化复杂的静态数据。例如:

test_container-level_comptime_expressions.zig
const first_25_primes = firstNPrimes(25);
const sum_of_first_25_primes = sum(&first_25_primes);

fn firstNPrimes(comptime n: usize) [n]i32 {
    var prime_list: [n]i32 = undefined;
    var next_index: usize = 0;
    var test_number: i32 = 2;
    while (next_index < prime_list.len) : (test_number += 1) {
        var test_prime_index: usize = 0;
        var is_prime = true;
        while (test_prime_index < next_index) : (test_prime_index += 1) {
            if (test_number % prime_list[test_prime_index] == 0) {
                is_prime = false;
                break;
            }
        }
        if (is_prime) {
            prime_list[next_index] = test_number;
            next_index += 1;
        }
    }
    return prime_list;
}

fn sum(numbers: []const i32) i32 {
    var result: i32 = 0;
    for (numbers) |x| {
        result += x;
    }
    return result;
}

test "variable values" {
    try @import("std").testing.expect(sum_of_first_25_primes == 1060);
}
Shell
$ zig test test_container-level_comptime_expressions.zig
1/1 test_container-level_comptime_expressions.test.variable values...OK
All 1 tests passed.

当我们编译这个程序时,Zig 生成了预先计算好答案的常量。以下是生成的 LLVM IR 中的行:

@0 = internal unnamed_addr constant [25 x i32] [i32 2, i32 3, i32 5, i32 7, i32 11, i32 13, i32 17, i32 19, i32 23, i32 29, i32 31, i32 37, i32 41, i32 43, i32 47, i32 53, i32 59, i32 61, i32 67, i32 71, i32 73, i32 79, i32 83, i32 89, i32 97]
@1 = internal unnamed_addr constant i32 1060

请注意,我们没有对这些函数的语法做任何特殊处理。例如,我们可以直接调用sum函数,并传入一个其长度和值仅在运行时才已知的一组数字切片。

泛型数据结构 §

Zig 使用编译时能力实现泛型数据结构,而无需引入任何特殊语法。

这是一个泛型List数据结构的示例。

generic_data_structure.zig
fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

// The generic List data structure can be instantiated by passing in a type:
var buffer: [10]i32 = undefined;
var list = List(i32){
    .items = &buffer,
    .len = 0,
};

就是这样。它是一个返回匿名struct的函数。为了错误消息和调试的目的,Zig 会根据创建匿名结构体时调用的函数名和参数推断出名称"List(i32)"

要显式地给类型命名,我们将其赋值给一个常量。

anonymous_struct_name.zig
const Node = struct {
    next: ?*Node,
    name: []const u8,
};

var node_a = Node{
    .next = null,
    .name = "Node A",
};

var node_b = Node{
    .next = &node_a,
    .name = "Node B",
};

在此示例中,Node结构体引用自身。这之所以可行,是因为所有顶层声明都是顺序无关的。只要编译器能够确定结构体的大小,它就可以自由地引用自身。在这种情况下,Node以指针的形式引用自身,而指针在编译时具有明确的大小,因此运行良好。

案例研究:Zig 中的 print §

综合以上所有内容,让我们看看print在 Zig 中是如何工作的。

print.zig
const print = @import("std").debug.print;

const a_number: i32 = 1234;
const a_string = "foobar";

pub fn main() void {
    print("here is a string: '{s}' here is a number: {}\n", .{ a_string, a_number });
}
Shell
$ zig build-exe print.zig
$ ./print
here is a string: 'foobar' here is a number: 1234

让我们深入了解一下它的实现原理:

poc_print_fn.zig
const Writer = struct {
    /// Calls print and then flushes the buffer.
    pub fn print(self: *Writer, comptime format: []const u8, args: anytype) anyerror!void {
        const State = enum {
            start,
            open_brace,
            close_brace,
        };

        comptime var start_index: usize = 0;
        comptime var state = State.start;
        comptime var next_arg: usize = 0;

        inline for (format, 0..) |c, i| {
            switch (state) {
                State.start => switch (c) {
                    '{' => {
                        if (start_index < i) try self.write(format[start_index..i]);
                        state = State.open_brace;
                    },
                    '}' => {
                        if (start_index < i) try self.write(format[start_index..i]);
                        state = State.close_brace;
                    },
                    else => {},
                },
                State.open_brace => switch (c) {
                    '{' => {
                        state = State.start;
                        start_index = i;
                    },
                    '}' => {
                        try self.printValue(args[next_arg]);
                        next_arg += 1;
                        state = State.start;
                        start_index = i + 1;
                    },
                    's' => {
                        continue;
                    },
                    else => @compileError("Unknown format character: " ++ [1]u8{c}),
                },
                State.close_brace => switch (c) {
                    '}' => {
                        state = State.start;
                        start_index = i;
                    },
                    else => @compileError("Single '}' encountered in format string"),
                },
            }
        }
        comptime {
            if (args.len != next_arg) {
                @compileError("Unused arguments");
            }
            if (state != State.start) {
                @compileError("Incomplete format string: " ++ format);
            }
        }
        if (start_index < format.len) {
            try self.write(format[start_index..format.len]);
        }
        try self.flush();
    }

    fn write(self: *Writer, value: []const u8) !void {
        _ = self;
        _ = value;
    }
    pub fn printValue(self: *Writer, value: anytype) !void {
        _ = self;
        _ = value;
    }
    fn flush(self: *Writer) !void {
        _ = self;
    }
};

这是一个概念验证实现;标准库中的实际函数具有更多格式化功能。

请注意,这不是硬编码在 Zig 编译器中;这是标准库中的用户层代码。

当这个函数从我们上面示例代码中分析时,Zig 会部分评估该函数并发出一个实际看起来像这样的函数:

发出的 print 函数
pub fn print(self: *Writer, arg0: []const u8, arg1: i32) !void {
    try self.write("here is a string: '");
    try self.printValue(arg0);
    try self.write("' here is a number: ");
    try self.printValue(arg1);
    try self.write("\n");
    try self.flush();
}

printValue是一个接受任何类型参数的函数,并根据类型执行不同的操作。

poc_printValue_fn.zig
const Writer = struct {
    pub fn printValue(self: *Writer, value: anytype) !void {
        switch (@typeInfo(@TypeOf(value))) {
            .int => {
                return self.writeInt(value);
            },
            .float => {
                return self.writeFloat(value);
            },
            .pointer => {
                return self.write(value);
            },
            else => {
                @compileError("Unable to print type '" ++ @typeName(@TypeOf(value)) ++ "'");
            },
        }
    }

    fn write(self: *Writer, value: []const u8) !void {
        _ = self;
        _ = value;
    }
    fn writeInt(self: *Writer, value: anytype) !void {
        _ = self;
        _ = value;
    }
    fn writeFloat(self: *Writer, value: anytype) !void {
        _ = self;
        _ = value;
    }
};

那么,如果我们给print太多参数会发生什么呢?

test_print_too_many_args.zig
const print = @import("std").debug.print;

const a_number: i32 = 1234;
const a_string = "foobar";

test "print too many arguments" {
    print("here is a string: '{s}' here is a number: {}\n", .{
        a_string,
        a_number,
        a_number,
    });
}
Shell
$ zig test test_print_too_many_args.zig
/home/andy/dev/zig/lib/std/fmt.zig:211:18: error: unused argument in 'here is a string: '{s}' here is a number: {}
                                                  '
            1 => @compileError("unused argument in '" ++ fmt ++ "'"),
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    print__anon_680: /home/andy/dev/zig/lib/std/io/Writer.zig:24:26
    print__anon_420: /home/andy/dev/zig/lib/std/io.zig:312:47
    1 reference(s) hidden; use '-freference-trace=3' to see all references

Zig 为程序员提供了防止自身错误所需的工具。

Zig 不关心格式参数是否是字符串字面量,只关心它是否是一个编译时已知值,并且可以强制转换为[]const u8

print_comptime-known_format.zig
const print = @import("std").debug.print;

const a_number: i32 = 1234;
const a_string = "foobar";
const fmt = "here is a string: '{s}' here is a number: {}\n";

pub fn main() void {
    print(fmt, .{ a_string, a_number });
}
Shell
$ zig build-exe print_comptime-known_format.zig
$ ./print_comptime-known_format
here is a string: 'foobar' here is a number: 1234

这工作得很好。

Zig 不会在编译器中特殊处理字符串格式化,而是暴露足够的强大功能,以便在用户空间中完成此任务。它这样做没有在 Zig 之上引入另一种语言,例如宏语言或预处理器语言。它完全是 Zig。

另请参阅

汇编 §

对于某些用例,可能需要直接控制 Zig 程序生成的机器代码,而不是依赖 Zig 的代码生成。对于这些情况,可以使用内联汇编。以下是一个在 x86_64 Linux 上使用内联汇编实现 Hello, World 的示例:

inline_assembly.zig
pub fn main() noreturn {
    const msg = "hello world\n";
    _ = syscall3(SYS_write, STDOUT_FILENO, @intFromPtr(msg), msg.len);
    _ = syscall1(SYS_exit, 0);
    unreachable;
}

pub const SYS_write = 1;
pub const SYS_exit = 60;

pub const STDOUT_FILENO = 1;

pub fn syscall1(number: usize, arg1: usize) usize {
    return asm volatile ("syscall"
        : [ret] "={rax}" (-> usize),
        : [number] "{rax}" (number),
          [arg1] "{rdi}" (arg1),
        : "rcx", "r11"
    );
}

pub fn syscall3(number: usize, arg1: usize, arg2: usize, arg3: usize) usize {
    return asm volatile ("syscall"
        : [ret] "={rax}" (-> usize),
        : [number] "{rax}" (number),
          [arg1] "{rdi}" (arg1),
          [arg2] "{rsi}" (arg2),
          [arg3] "{rdx}" (arg3),
        : "rcx", "r11"
    );
}
Shell
$ zig build-exe inline_assembly.zig -target x86_64-linux
$ ./inline_assembly
hello world

解析语法

Assembly Syntax Explained.zig
pub fn syscall1(number: usize, arg1: usize) usize {
    // Inline assembly is an expression which returns a value.
    // the `asm` keyword begins the expression.
    return asm
    // `volatile` is an optional modifier that tells Zig this
    // inline assembly expression has side-effects. Without
    // `volatile`, Zig is allowed to delete the inline assembly
    // code if the result is unused.
    volatile (
    // Next is a comptime string which is the assembly code.
    // Inside this string one may use `%[ret]`, `%[number]`,
    // or `%[arg1]` where a register is expected, to specify
    // the register that Zig uses for the argument or return value,
    // if the register constraint strings are used. However in
    // the below code, this is not used. A literal `%` can be
    // obtained by escaping it with a double percent: `%%`.
    // Often multiline string syntax comes in handy here.
        \\syscall
        // Next is the output. It is possible in the future Zig will
        // support multiple outputs, depending on how
        // https://github.com/ziglang/zig/issues/215 is resolved.
        // It is allowed for there to be no outputs, in which case
        // this colon would be directly followed by the colon for the inputs.
        :
        // This specifies the name to be used in `%[ret]` syntax in
        // the above assembly string. This example does not use it,
        // but the syntax is mandatory.
          [ret]
          // Next is the output constraint string. This feature is still
          // considered unstable in Zig, and so LLVM/GCC documentation
          // must be used to understand the semantics.
          // http://releases.llvm.org/10.0.0/docs/LangRef.html#inline-asm-constraint-string
          // https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
          // In this example, the constraint string means "the result value of
          // this inline assembly instruction is whatever is in $rax".
          "={rax}"
          // Next is either a value binding, or `->` and then a type. The
          // type is the result type of the inline assembly expression.
          // If it is a value binding, then `%[ret]` syntax would be used
          // to refer to the register bound to the value.
          (-> usize),
          // Next is the list of inputs.
          // The constraint for these inputs means, "when the assembly code is
          // executed, $rax shall have the value of `number` and $rdi shall have
          // the value of `arg1`". Any number of input parameters is allowed,
          // including none.
        : [number] "{rax}" (number),
          [arg1] "{rdi}" (arg1),
          // Next is the list of clobbers. These declare a set of registers whose
          // values will not be preserved by the execution of this assembly code.
          // These do not include output or input registers. The special clobber
          // value of "memory" means that the assembly writes to arbitrary undeclared
          // memory locations - not only the memory pointed to by a declared indirect
          // output. In this example we list $rcx and $r11 because it is known the
          // kernel syscall does not preserve these registers.
        : "rcx", "r11"
    );
}

对于 x86 和 x86_64 目标,使用的是 AT&T 语法,而不是更流行的 Intel 语法。这是由于技术限制;汇编解析由 LLVM 提供,其对 Intel 语法的支持存在 Bug 且未经充分测试。

某天 Zig 可能会有自己的汇编器。这将使其能够更无缝地集成到语言中,并与流行的 NASM 语法兼容。本文档部分将在 1.0.0 发布之前更新,并对 AT&T 与 Intel/NASM 语法的状态作出结论性声明。

输出约束 §

输出约束在 Zig 中仍被认为是不稳定的,因此必须使用LLVM 文档GCC 文档来理解其语义。

请注意,计划通过issue #215对输出约束进行一些重大更改。

输入约束 §

输入约束在 Zig 中仍被认为是不稳定的,因此必须使用LLVM 文档GCC 文档来理解其语义。

请注意,计划通过issue #215对输入约束进行一些重大更改。

寄存器损坏列表 §

寄存器损坏列表是指汇编代码执行后其值不会保留的寄存器集合。这不包括输出或输入寄存器。特殊的损坏值 "memory" 意味着汇编导致写入任意未声明的内存位置——而不仅仅是声明的间接输出所指向的内存。

未能为给定的内联汇编表达式声明完整的寄存器损坏列表是未检查的非法行为

全局汇编 §

当汇编表达式出现在容器级别的编译期块中时,这就是**全局汇编**。

这种汇编的规则与内联汇编不同。首先,volatile 无效,因为所有全局汇编都是无条件包含的。其次,没有输入、输出或损坏列表。所有全局汇编都逐字连接成一个长字符串并一起汇编。关于 % 的模板替换规则不像内联汇编表达式中那样存在。

test_global_assembly.zig
const std = @import("std");
const expect = std.testing.expect;

comptime {
    asm (
        \\.global my_func;
        \\.type my_func, @function;
        \\my_func:
        \\  lea (%rdi,%rsi,1),%eax
        \\  retq
    );
}

extern fn my_func(a: i32, b: i32) i32;

test "global assembly" {
    try expect(my_func(12, 34) == 46);
}
Shell
$ zig test test_global_assembly.zig -target x86_64-linux
1/1 test_global_assembly.test.global assembly...OK
All 1 tests passed.

原子操作 §

TODO: @atomic rmw

TODO: 内置原子内存排序枚举

另请参阅

异步函数 §

异步函数在 0.11.0 版本发布后出现了问题。由于多个未解决的问题,它们在 Zig 语言中的未来尚不明确:

  • LLVM 无法优化它们。
  • 第三方调试器无法调试它们。
  • 取消问题.
  • 异步函数指针阻止栈大小已知。

这些问题是可以克服的,但这需要时间。Zig 团队目前专注于其他优先事项。

内置函数 §

内置函数由编译器提供,并以 @ 为前缀。参数上的 comptime 关键字表示该参数必须在编译时已知。

@addrSpaceCast §

@addrSpaceCast(ptr: anytype) anytype

将一个指针从一个地址空间转换为另一个地址空间。新的地址空间根据结果类型推断。根据当前目标和地址空间,此转换可能是一个空操作、一个复杂操作或非法操作。如果转换合法,则生成的指针指向与指针操作数相同的内存位置。在相同的地址空间之间转换指针总是有效的。

@addWithOverflow §

@addWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }

执行 a + b 并返回一个包含结果和可能溢出位的元组。

@alignCast §

@alignCast(ptr: anytype) anytype

ptr 可以是 *T?*T[]T。更改指针的对齐方式。要使用的对齐方式根据结果类型推断。

生成的代码中会添加一个指针对齐安全检查,以确保指针按承诺对齐。

@alignOf §

@alignOf(comptime T: type) comptime_int

此函数返回此类型对于当前目标应按 C ABI 对齐的字节数。当指针的子类型具有此对齐方式时,可以从类型中省略对齐方式。

const assert = @import("std").debug.assert;
comptime {
    assert(*u32 == *align(@alignOf(u32)) u32);
}

结果是一个目标特定的编译时常量。它保证小于或等于@sizeOf(T)

另请参阅

@as §

@as(comptime T: type, expression) T

执行类型强制转换。当转换是明确且安全时,允许此转换,并且只要可能,它是类型之间转换的首选方式。

@atomicLoad §

@atomicLoad(comptime T: type, ptr: *const T, comptime ordering: AtomicOrder) T

此内置函数原子地解引用指向 T 的指针并返回值。

T 必须是一个指针、一个 bool、一个浮点数、一个整数或一个枚举。

AtomicOrder 可以通过 @import("std").builtin.AtomicOrder 找到。

另请参阅

@atomicRmw §

@atomicRmw(comptime T: type, ptr: *T, comptime op: AtomicRmwOp, operand: T, comptime ordering: AtomicOrder) T

此内置函数解引用指向 T 的指针并原子地修改值并返回旧值。

T 必须是一个指针、一个 bool、一个浮点数、一个整数或一个枚举。

AtomicOrder 可以通过 @import("std").builtin.AtomicOrder 找到。

AtomicRmwOp 可以通过 @import("std").builtin.AtomicRmwOp 找到。

另请参阅

@atomicStore §

@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: AtomicOrder) void

此内置函数解引用指向 T 的指针并原子地存储给定值。

T 必须是一个指针、一个 bool、一个浮点数、一个整数或一个枚举。

AtomicOrder 可以通过 @import("std").builtin.AtomicOrder 找到。

另请参阅

@bitCast §

@bitCast(value: anytype) anytype

将一种类型的值转换为另一种类型。返回类型是推断的结果类型。

断言 @sizeOf(@TypeOf(value)) == @sizeOf(DestType)

断言 @typeInfo(DestType) != .pointer。如果需要,请使用 @ptrCast@ptrFromInt

例如,可用于以下事项:

  • f32 转换为 u32
  • i32 转换为 u32 并保留二进制补码

如果 value 在编译时已知,则在编译时起作用。位铸造具有未定义布局的值是编译错误;这意味着,除了具有专用铸造内置函数的类型(枚举、指针、错误集)之外,裸结构体、错误联合体、切片、可选类型以及任何其他没有明确内存布局的类型也不能用于此操作。

@bitOffsetOf §

@bitOffsetOf(comptime T: type, comptime field_name: []const u8) comptime_int

返回字段相对于其包含结构体的位偏移量。

对于非packed structs,此值将始终可被 8 整除。对于 packed structs,非字节对齐的字段将共享一个字节偏移量,但它们将具有不同的位偏移量。

另请参阅

@bitSizeOf §

@bitSizeOf(comptime T: type) comptime_int

此函数返回在内存中存储 T 所需的位数(如果该类型是 packed struct/union 中的一个字段)。结果是一个目标特定的编译时常量。

此函数在运行时测量大小。对于运行时不允许的类型,例如 comptime_inttype,结果为 0

另请参阅

@branchHint §

@branchHint(hint: BranchHint) void

向优化器提示给定控制流分支被执行的可能性。

BranchHint 可以通过 @import("std").builtin.BranchHint 找到。

此函数仅在控制流分支中的第一条语句或函数中的第一条语句中有效。

@breakpoint §

@breakpoint() void

此函数插入一个平台特定的调试陷阱指令,导致调试器在此处中断。与 @trap() 不同,如果程序恢复,执行可以在此点之后继续。

此函数仅在函数作用域内有效。

另请参阅

@mulAdd §

@mulAdd(comptime T: type, a: T, b: T, c: T) T

融合乘加,类似于 (a * b) + c,但只舍入一次,因此更精确。

支持浮点数和浮点数的向量

@byteSwap §

@byteSwap(operand: anytype) T

@TypeOf(operand) 必须是整数类型或位计数可被 8 整除的整数向量类型。

operand 可以是整数向量

交换整数的字节顺序。这将大端整数转换为小端整数,并从小端整数转换为大端整数。

请注意,为了内存布局与字节序相关,整数类型应与@sizeOf报告的字节数相关。这在 u24 中得到了证明。@sizeOf(u24) == 4,这意味着存储在内存中的 u24 占用 4 个字节,并且这 4 个字节在小端与大端系统之间进行交换。另一方面,如果 T 被指定为 u24,则只反转 3 个字节。

@bitReverse §

@bitReverse(integer: anytype) T

@TypeOf(anytype) 接受任何整数类型或整数向量类型。

反转整数值的位模式,如果适用则包括符号位。

例如,0b10110110 (u8 = 182, i8 = -74) 变为 0b01101101 (u8 = 109, i8 = 109)。

@offsetOf §

@offsetOf(comptime T: type, comptime field_name: []const u8) comptime_int

返回字段相对于其包含结构体的字节偏移量。

另请参阅

@call §

@call(modifier: std.builtin.CallModifier, function: anytype, args: anytype) anytype

调用一个函数,与带括号的表达式调用方式相同

test_call_builtin.zig
const expect = @import("std").testing.expect;

test "noinline function call" {
    try expect(@call(.auto, add, .{ 3, 9 }) == 12);
}

fn add(a: i32, b: i32) i32 {
    return a + b;
}
Shell
$ zig test test_call_builtin.zig
1/1 test_call_builtin.test.noinline function call...OK
All 1 tests passed.

@call 比普通的函数调用语法更灵活。CallModifier 枚举在此处重现

builtin.CallModifier struct.zig
pub const CallModifier = enum {
    /// Equivalent to function call syntax.
    auto,

    /// Equivalent to async keyword used with function call syntax.
    async_kw,

    /// Prevents tail call optimization. This guarantees that the return
    /// address will point to the callsite, as opposed to the callsite's
    /// callsite. If the call is otherwise required to be tail-called
    /// or inlined, a compile error is emitted instead.
    never_tail,

    /// Guarantees that the call will not be inlined. If the call is
    /// otherwise required to be inlined, a compile error is emitted instead.
    never_inline,

    /// Asserts that the function call will not suspend. This allows a
    /// non-async function to call an async function.
    no_async,

    /// Guarantees that the call will be generated with tail call optimization.
    /// If this is not possible, a compile error is emitted instead.
    always_tail,

    /// Guarantees that the call will be inlined at the callsite.
    /// If this is not possible, a compile error is emitted instead.
    always_inline,

    /// Evaluates the call at compile-time. If the call cannot be completed at
    /// compile-time, a compile error is emitted instead.
    compile_time,
};

@cDefine §

@cDefine(comptime name: []const u8, value) void

此函数只能出现在 @cImport 内部。

这会将 #define $name $value 添加到 @cImport 临时缓冲区。

如果要在没有值的情况下定义,像这样:

#define _GNU_SOURCE

使用 void 值,像这样:

@cDefine("_GNU_SOURCE", {})

另请参阅

@cImport §

@cImport(expression) type

此函数解析 C 代码并将函数、类型、变量和兼容的宏定义导入到一个新的空结构体类型中,然后返回该类型。

expression 在编译时解释。内置函数 @cInclude@cDefine@cUndef 在此表达式中工作,将内容附加到临时缓冲区,然后将其解析为 C 代码。

通常,您的整个应用程序中应该只有一个 @cImport,因为这可以节省编译器多次调用 clang,并防止内联函数重复。

拥有多个 @cImport 表达式的原因包括:

  • 为了避免符号冲突,例如 foo.h 和 bar.h 都 #define CONNECTION_COUNT
  • 使用不同的预处理器定义来分析 C 代码

另请参阅

@cInclude §

@cInclude(comptime path: []const u8) void

此函数只能出现在 @cImport 内部。

这会将 #include <$path>\n 附加到 c_import 临时缓冲区。

另请参阅

@clz §

@clz(operand: anytype) anytype

@TypeOf(operand) 必须是整数类型或整数向量类型。

operand 可以是整数向量

计算整数中最重要(在大端意义上是前导)的零的数目——“计数前导零”。

返回类型是一个无符号整数或无符号整数向量,其位数为可表示整数类型位数的最小值。

如果 operand 为零,@clz 返回整数类型 T 的位宽。

另请参阅

@cmpxchgStrong §

@cmpxchgStrong(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T

此函数执行一个强原子比较和交换操作,如果当前值是给定的预期值,则返回 null。它等同于以下代码,只是它是原子操作:

not_atomic_cmpxchgStrong.zig
fn cmpxchgStrongButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
    const old_value = ptr.*;
    if (old_value == expected_value) {
        ptr.* = new_value;
        return null;
    } else {
        return old_value;
    }
}

如果你在重试循环中使用 cmpxchg,@cmpxchgWeak 是更好的选择,因为它可以更高效地在机器指令中实现。

T 必须是一个指针、一个 bool、一个浮点数、一个整数或一个枚举。

@typeInfo(@TypeOf(ptr)).pointer.alignment 必须 >= @sizeOf(T).

AtomicOrder 可以通过 @import("std").builtin.AtomicOrder 找到。

另请参阅

@cmpxchgWeak §

@cmpxchgWeak(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T

此函数执行一个弱原子比较和交换操作,如果当前值是给定的预期值,则返回 null。它等同于以下代码,只是它是原子操作:

cmpxchgWeakButNotAtomic
fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_value: T) ?T {
    const old_value = ptr.*;
    if (old_value == expected_value and usuallyTrueButSometimesFalse()) {
        ptr.* = new_value;
        return null;
    } else {
        return old_value;
    }
}

如果你在重试循环中使用 cmpxchg,偶发性失败不是问题,并且 cmpxchgWeak 是更好的选择,因为它可以更高效地在机器指令中实现。但是,如果你需要更强的保证,请使用@cmpxchgStrong

T 必须是一个指针、一个 bool、一个浮点数、一个整数或一个枚举。

@typeInfo(@TypeOf(ptr)).pointer.alignment 必须 >= @sizeOf(T).

AtomicOrder 可以通过 @import("std").builtin.AtomicOrder 找到。

另请参阅

@compileError §

@compileError(comptime msg: []const u8) noreturn

此函数在语义分析时,会引发一个编译错误,并显示消息 msg

有几种方法可以避免代码被语义检查,例如使用带有编译时常量的 ifswitch,以及 comptime 函数。

@compileLog §

@compileLog(...) void

此函数在编译时打印传递给它的参数。

为了防止在代码库中意外留下编译日志语句,构建时会添加一个编译错误,指向该编译日志语句。此错误会阻止代码生成,但不会干扰分析。

此函数可用于对编译时执行的代码进行“printf 调试”。

test_compileLog_builtin.zig
const print = @import("std").debug.print;

const num1 = blk: {
    var val1: i32 = 99;
    @compileLog("comptime val1 = ", val1);
    val1 = val1 + 1;
    break :blk val1;
};

test "main" {
    @compileLog("comptime in main");

    print("Runtime in main, num1 = {}.\n", .{num1});
}
Shell
$ zig test test_compileLog_builtin.zig
/home/andy/dev/zig/doc/langref/test_compileLog_builtin.zig:11:5: error: found compile log statement
    @compileLog("comptime in main");
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_compileLog_builtin.zig:5:5: note: also here
    @compileLog("comptime val1 = ", val1);
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Compile Log Output:
@as(*const [16:0]u8, "comptime in main")
@as(*const [16:0]u8, "comptime val1 = "), @as(i32, 99)

@constCast §

@constCast(value: anytype) DestType

从指针中移除 const 限定符。

@ctz §

@ctz(operand: anytype) anytype

@TypeOf(operand) 必须是整数类型或整数向量类型。

operand 可以是整数向量

计算整数中最不重要(在大端意义上是尾随)的零的数目——“计数尾随零”。

返回类型是一个无符号整数或无符号整数向量,其位数为可表示整数类型位数的最小值。

如果 operand 为零,@ctz 返回整数类型 T 的位宽。

另请参阅

@cUndef §

@cUndef(comptime name: []const u8) void

此函数只能出现在 @cImport 内部。

这会将 #undef $name 附加到 @cImport 临时缓冲区。

另请参阅

@cVaArg §

@cVaArg(operand: *std.builtin.VaList, comptime T: type) T

实现 C 宏 va_arg

另请参阅

@cVaCopy §

@cVaCopy(src: *std.builtin.VaList) std.builtin.VaList

实现 C 宏 va_copy

另请参阅

@cVaEnd §

@cVaEnd(src: *std.builtin.VaList) void

实现 C 宏 va_end

另请参阅

@cVaStart §

@cVaStart() std.builtin.VaList

实现 C 宏 va_start。仅在可变参数函数中有效。

另请参阅

@divExact §

@divExact(numerator: T, denominator: T) T

精确除法。调用者保证 denominator != 0@divTrunc(numerator, denominator) * denominator == numerator

  • @divExact(6, 3) == 2
  • @divExact(a, b) * b == a

对于返回可能错误代码的函数,请使用 @import("std").math.divExact

另请参阅

@divFloor §

@divFloor(numerator: T, denominator: T) T

向下取整除法。向负无穷大方向舍入。对于无符号整数,它与 numerator / denominator 相同。调用者保证 denominator != 0!(@typeInfo(T) == .int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)

  • @divFloor(-5, 3) == -2
  • (@divFloor(a, b) * b) + @mod(a, b) == a

对于返回可能错误代码的函数,请使用 @import("std").math.divFloor

另请参阅

@divTrunc §

@divTrunc(numerator: T, denominator: T) T

截断除法。向零舍入。对于无符号整数,它与 numerator / denominator 相同。调用者保证 denominator != 0!(@typeInfo(T) == .int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1)

  • @divTrunc(-5, 3) == -1
  • (@divTrunc(a, b) * b) + @rem(a, b) == a

对于返回可能错误代码的函数,请使用 @import("std").math.divTrunc

另请参阅

@embedFile §

@embedFile(comptime path: []const u8) *const [N:0]u8

此函数返回一个编译时常量指针,指向一个以 null 结尾、固定大小的数组,其长度等于给定 path 文件的字节数。数组的内容是文件内容。这等同于一个包含文件内容的字符串字面量

path 是绝对路径或相对于当前文件的相对路径,就像 @import 一样。

另请参阅

@enumFromInt §

@enumFromInt(integer: anytype) anytype

将整数转换为枚举值。返回类型是推断的结果类型。

尝试转换一个在枚举中没有相应值的整数会触发安全检查的非法行为。请注意,非穷举枚举对其枚举的整数标签类型中的所有整数都具有对应值:_ 值表示枚举标签类型中所有剩余的未命名整数。

另请参阅

@errorFromInt §

@errorFromInt(value: std.meta.Int(.unsigned, @bitSizeOf(anyerror))) anyerror

将错误的整数表示转换为全局错误集类型。

通常建议避免这种转换,因为错误的整数表示在源代码更改后不稳定。

尝试转换不对应任何错误的整数会导致安全检查的非法行为

另请参阅

@errorName §

@errorName(err: anyerror) [:0]const u8

此函数返回错误的字符串表示。error.OutOfMem 的字符串表示为 "OutOfMem"

如果整个应用程序中没有调用 @errorName,或者所有调用中 err 的值都是编译时已知的,则不会生成错误名称表。

@errorReturnTrace §

@errorReturnTrace() ?*builtin.StackTrace

如果二进制文件以错误返回跟踪方式构建,并且此函数在调用具有错误或错误联合返回类型的函数中被调用,则返回一个堆栈跟踪对象。否则返回null

@errorCast §

@errorCast(value: anytype) anytype

将一个错误集或错误联合体值从一个错误集转换为另一个错误集。返回类型是推断的结果类型。尝试转换不在目标错误集中的错误会导致安全检查的非法行为

@export §

@export(comptime ptr: *const anyopaque, comptime options: std.builtin.ExportOptions) void

在输出目标文件中创建一个指向 ptr 目标的符号。

ptr 必须指向一个全局变量或一个编译时已知的常量。

此内置函数可以从编译期块中调用,以有条件地导出符号。当 ptr 指向一个具有 C 调用约定且 options.linkage.Strong 的函数时,这等同于在函数上使用 export 关键字:

export_builtin.zig
comptime {
    @export(&internalName, .{ .name = "foo", .linkage = .strong });
}

fn internalName() callconv(.C) void {}
Shell
$ zig build-obj export_builtin.zig

这等价于:

export_builtin_equivalent_code.zig
export fn foo() void {}
Shell
$ zig build-obj export_builtin_equivalent_code.zig

请注意,即使使用 export标识符@"foo" 语法也可用于为符号名称选择任何字符串

export_any_symbol_name.zig
export fn @"A function name that is a complete sentence."() void {}
Shell
$ zig build-obj export_any_symbol_name.zig

查看结果对象时,您可以看到符号是逐字使用的

00000000000001f0 T A function name that is a complete sentence.

另请参阅

@extern §

@extern(T: type, comptime options: std.builtin.ExternOptions) T

在输出目标文件中创建对外部符号的引用。T 必须是指针类型。

另请参阅

@field §

@field(lhs: anytype, comptime field_name: []const u8) (field)

通过编译时字符串执行字段访问。适用于字段和声明。

test_field_builtin.zig
const std = @import("std");

const Point = struct {
    x: u32,
    y: u32,

    pub var z: u32 = 1;
};

test "field access by string" {
    const expect = std.testing.expect;
    var p = Point{ .x = 0, .y = 0 };

    @field(p, "x") = 4;
    @field(p, "y") = @field(p, "x") + 1;

    try expect(@field(p, "x") == 4);
    try expect(@field(p, "y") == 5);
}

test "decl access by string" {
    const expect = std.testing.expect;

    try expect(@field(Point, "z") == 1);

    @field(Point, "z") = 2;
    try expect(@field(Point, "z") == 2);
}
Shell
$ zig test test_field_builtin.zig
1/2 test_field_builtin.test.field access by string...OK
2/2 test_field_builtin.test.decl access by string...OK
All 2 tests passed.

@fieldParentPtr §

@fieldParentPtr(comptime field_name: []const u8, field_ptr: *T) anytype

给定一个指向结构体字段的指针,返回一个指向包含该字段的结构体的指针。返回类型(以及相关的结构体)是推断的结果类型。

如果 field_ptr 不指向结果类型实例的 field_name 字段,并且结果类型具有未明确定义的布局,则会触发未检查的非法行为

@FieldType §

@FieldType(comptime Type: type, comptime field_name: []const u8) type

给定一个类型及其字段名称,返回该字段的类型。

@floatCast §

@floatCast(value: anytype) anytype

将一种浮点类型转换为另一种浮点类型。此转换是安全的,但可能导致数值失去精度。返回类型是推断的结果类型。

@floatFromInt §

@floatFromInt(int: anytype) anytype

将整数转换为最接近的浮点表示。返回类型是推断的结果类型。要进行反向转换,请使用@intFromFloat。此操作对于所有整数类型的所有值都是合法的。

@frameAddress §

@frameAddress() usize

此函数返回当前栈帧的基指针。

其含义是目标特定的,并且并非在所有平台上都一致。由于激进的优化,在发布模式下可能无法获取帧地址。

此函数仅在函数作用域内有效。

@hasDecl §

@hasDecl(comptime Container: type, comptime name: []const u8) bool

返回容器是否包含与 name 匹配的声明。

test_hasDecl_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

const Foo = struct {
    nope: i32,

    pub var blah = "xxx";
    const hi = 1;
};

test "@hasDecl" {
    try expect(@hasDecl(Foo, "blah"));

    // Even though `hi` is private, @hasDecl returns true because this test is
    // in the same file scope as Foo. It would return false if Foo was declared
    // in a different file.
    try expect(@hasDecl(Foo, "hi"));

    // @hasDecl is for declarations; not fields.
    try expect(!@hasDecl(Foo, "nope"));
    try expect(!@hasDecl(Foo, "nope1234"));
}
Shell
$ zig test test_hasDecl_builtin.zig
1/1 test_hasDecl_builtin.test.@hasDecl...OK
All 1 tests passed.

另请参阅

@hasField §

@hasField(comptime Container: type, comptime name: []const u8) bool

返回结构体、联合体或枚举的字段名是否存在。

结果是一个编译时常量。

它不包括函数、变量或常量。

另请参阅

@import §

@import(comptime path: []const u8) type

此函数查找与 path 对应的 zig 文件,如果尚未添加,则将其添加到构建中。

Zig 源文件隐式为结构体,其名称等于文件不带扩展名的基本名称。@import 返回与文件对应的结构体类型。

带有 pub 关键字的声明可以从与声明它们的文件不同的源文件中引用。

path 可以是相对路径,也可以是包的名称。如果是相对路径,则它相对于包含 @import 函数调用的文件。

以下包总是可用:

  • @import("std") - Zig 标准库
  • @import("builtin") - 目标特定信息。命令 zig build-exe --show-builtin 将源输出到 stdout 以供参考。
  • @import("root") - 根源文件。通常是 src/main.zig,但取决于构建的文件。

另请参阅

@inComptime §

@inComptime() bool

返回内置函数是否在 comptime 上下文中运行。结果是一个编译时常量。

这可以用于提供替代的、编译时友好的函数实现。例如,不应将其用于将某些函数排除在编译时评估之外。

另请参阅

@intCast §

@intCast(int: anytype) anytype

将一个整数转换为另一个整数,同时保持相同的数值。返回类型是推断的结果类型。尝试转换超出目标类型范围的数字会导致安全检查的非法行为

test_intCast_builtin.zig
test "integer cast panic" {
    var a: u16 = 0xabcd; // runtime-known
    _ = &a;
    const b: u8 = @intCast(a);
    _ = b;
}
Shell
$ zig test test_intCast_builtin.zig
1/1 test_intCast_builtin.test.integer cast panic...thread 202657 panic: integer cast truncated bits
/home/andy/dev/zig/doc/langref/test_intCast_builtin.zig:4:19: 0x10488e8 in test.integer cast panic (test)
    const b: u8 = @intCast(a);
                  ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef095 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e747d in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e68f2 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e64cd in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/ac2c242ce1003dd9a94c75602d015a33/test --seed=0x6cbda8e7

要截断超出目标类型范围的数字的有效位,请使用@truncate

如果 Tcomptime_int,则这在语义上等同于类型强制转换

@intFromBool §

@intFromBool(value: bool) u1

true 转换为 @as(u1, 1),将 false 转换为 @as(u1, 0)

@intFromEnum §

@intFromEnum(enum_or_tagged_union: anytype) anytype

将枚举值转换为其整数标签类型。当传递带标签的联合体时,标签值用作枚举值。

如果只有一个可能的枚举值,结果将是编译时已知的 comptime_int

另请参阅

@intFromError §

@intFromError(err: anytype) std.meta.Int(.unsigned, @bitSizeOf(anyerror))

支持以下类型:

将错误转换为错误的整数表示。

通常建议避免这种转换,因为错误的整数表示在源代码更改后不稳定。

另请参阅

@intFromFloat §

@intFromFloat(float: anytype) anytype

将浮点数的整数部分转换为推断的结果类型。

如果浮点数的整数部分不能适应目标类型,它会触发安全检查的非法行为

另请参阅

@intFromPtr §

@intFromPtr(value: anytype) usize

value 转换为 usize,它是指针的地址。value 可以是 *T?*T

要进行反向转换,请使用@ptrFromInt

@max §

@max(...) T

接受两个或更多参数,并返回其中最大的值(最大值)。此内置函数接受整数、浮点数以及它们的向量。在后一种情况下,操作是逐元素执行的。

NaN 的处理方式如下:返回包含的最大非 NaN 值。如果所有操作数都是 NaN,则返回 NaN。

另请参阅

@memcpy §

@memcpy(noalias dest, noalias source) void

此函数将字节从一个内存区域复制到另一个内存区域。

dest 必须是可变切片、指向数组的可变指针或多项指针。它可以具有任何对齐方式,并且可以具有任何元素类型。

source 必须是切片、指向数组的指针或多项指针。它可以具有任何对齐方式,并且可以具有任何元素类型。

source 元素类型必须与 dest 元素类型具有相同的内存表示。

for循环类似,sourcedest 中至少一个必须提供长度,如果提供了两个长度,则它们必须相等。

最后,这两个内存区域不得重叠。

@memset §

@memset(dest, elem) void

此函数将内存区域的所有元素设置为 elem

dest 必须是可变切片或指向数组的可变指针。它可以具有任何对齐方式,并且可以具有任何元素类型。

elem 被强制转换为 dest 的元素类型。

要安全地从内存中清零敏感内容,您应该使用 std.crypto.secureZero

@min §

@min(...) T

接受两个或更多参数,并返回其中最小的值(最小值)。此内置函数接受整数、浮点数以及它们的向量。在后一种情况下,操作是逐元素执行的。

NaN 的处理方式如下:返回包含的最小非 NaN 值。如果所有操作数都是 NaN,则返回 NaN。

另请参阅

@wasmMemorySize §

@wasmMemorySize(index: u32) usize

此函数返回由 index 标识的 Wasm 内存的大小,以 Wasm 页为单位的无符号值。请注意,每个 Wasm 页的大小为 64KB。

此函数是一个低级内在函数,没有通常对针对 Wasm 的分配器设计者有用的安全机制。因此,除非您正在从头编写新的分配器,否则您应该使用类似 @import("std").heap.WasmPageAllocator 的东西。

另请参阅

@wasmMemoryGrow §

@wasmMemoryGrow(index: u32, delta: usize) isize

此函数将由 index 标识的 Wasm 内存大小增加 delta,以无符号的 Wasm 页数为单位。请注意,每个 Wasm 页大小为 64KB。成功时,返回先前的内存大小;失败时,如果分配失败,返回 -1。

此函数是一个低级内在函数,没有通常对针对 Wasm 的分配器设计者有用的安全机制。因此,除非您正在从头编写新的分配器,否则您应该使用类似 @import("std").heap.WasmPageAllocator 的东西。

test_wasmMemoryGrow_builtin.zig
const std = @import("std");
const native_arch = @import("builtin").target.cpu.arch;
const expect = std.testing.expect;

test "@wasmMemoryGrow" {
    if (native_arch != .wasm32) return error.SkipZigTest;

    const prev = @wasmMemorySize(0);
    try expect(prev == @wasmMemoryGrow(0, 1));
    try expect(prev + 1 == @wasmMemorySize(0));
}
Shell
$ zig test test_wasmMemoryGrow_builtin.zig
1/1 test_wasmMemoryGrow_builtin.test.@wasmMemoryGrow...SKIP
0 passed; 1 skipped; 0 failed.

另请参阅

@mod §

@mod(numerator: T, denominator: T) T

取模运算。对于无符号整数,这与 numerator % denominator 相同。调用者保证 denominator > 0,否则在启用运行时安全检查时,操作将导致余数除以零

  • @mod(-5, 3) == 1
  • (@divFloor(a, b) * b) + @mod(a, b) == a

对于返回错误代码的函数,请参见 @import("std").math.mod

另请参阅

@mulWithOverflow §

@mulWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }

执行 a * b 并返回一个包含结果和可能溢出位的元组。

@panic §

@panic(message: []const u8) noreturn

调用 panic 处理函数。默认情况下,panic 处理函数调用根源文件中公开的公共 panic 函数,如果未指定,则调用 std/builtin.zig 中的 std.builtin.default_panic 函数。

通常最好使用 @import("std").debug.panic。但是,@panic 在以下两种情况下可能有用:

  • 从库代码中,如果程序员在根源文件中公开了 panic 函数,则调用它。
  • 当 C 和 Zig 代码混合时,跨多个 .o 文件调用规范的 panic 实现。

另请参阅

@popCount §

@popCount(operand: anytype) anytype

@TypeOf(operand) 必须是整数类型。

operand 可以是整数向量

计算整数中设置的位数——“群体计数”。

返回类型是一个无符号整数或无符号整数向量,其位数为可表示整数类型位数的最小值。

另请参阅

@prefetch §

@prefetch(ptr: anytype, comptime options: PrefetchOptions) void

此内置函数指示编译器在目标 CPU 支持时发出预取指令。如果目标 CPU 不支持请求的预取指令,则此内置函数是一个空操作。此函数对程序行为没有影响,只影响性能特征。

ptr 参数可以是任何指针类型,并确定要预取的内存地址。此函数不会解引用指针,将指向无效内存的指针传递给此函数是完全合法的,不会导致非法行为。

PrefetchOptions 可以通过 @import("std").builtin.PrefetchOptions 找到。

@ptrCast §

@ptrCast(value: anytype) anytype

将一种类型的指针转换为另一种类型的指针。返回类型是推断的结果类型。

允许使用可选指针。将为null的可选指针转换为非可选指针会触发安全检查的非法行为

@ptrCast 不能用于:

  • 移除 const 限定符,请使用@constCast
  • 移除 volatile 限定符,请使用@volatileCast
  • 更改指针地址空间,请使用@addrSpaceCast
  • 增加指针对齐,请使用@alignCast
  • 将非切片指针转换为切片,请使用切片语法 ptr[start..end]

@ptrFromInt §

@ptrFromInt(address: usize) anytype

将整数转换为指针。返回类型是推断的结果类型。要进行反向转换,请使用@intFromPtr。将地址 0 转换为非可选且不具有 allowzero 属性的目标类型将导致在启用运行时安全检查时出现指针转换空值无效的 panic。

如果目标指针类型不允许地址为零且 address 为零,则会触发安全检查的非法行为

@rem §

@rem(numerator: T, denominator: T) T

余数除法。对于无符号整数,这与 numerator % denominator 相同。调用者保证 denominator > 0,否则在启用运行时安全检查时,操作将导致余数除以零

  • @rem(-5, 3) == -2
  • (@divTrunc(a, b) * b) + @rem(a, b) == a

对于返回错误代码的函数,请参见 @import("std").math.rem

另请参阅

@returnAddress §

@returnAddress() usize

此函数返回当前函数返回时将执行的下一条机器代码指令的地址。

其含义是目标特定的,并且并非在所有平台上都一致。

此函数仅在函数作用域内有效。如果函数被内联到调用函数中,则返回的地址将适用于调用函数。

@select §

@select(comptime T: type, pred: @Vector(len, bool), a: @Vector(len, T), b: @Vector(len, T)) @Vector(len, T)

根据 pred 逐元素从 ab 中选择值。如果 pred[i]true,则结果中相应的元素将是 a[i],否则为 b[i]

另请参阅

@setEvalBranchQuota §

@setEvalBranchQuota(comptime new_quota: u32) void

增加编译时代码执行在放弃并引发编译错误之前可以使用的最大向后分支数。

如果 new_quota 小于默认配额 (1000) 或之前显式设置的配额,则会被忽略。

示例

test_without_setEvalBranchQuota_builtin.zig
test "foo" {
    comptime {
        var i = 0;
        while (i < 1001) : (i += 1) {}
    }
}
Shell
$ zig test test_without_setEvalBranchQuota_builtin.zig
/home/andy/dev/zig/doc/langref/test_without_setEvalBranchQuota_builtin.zig:4:9: error: evaluation exceeded 1000 backwards branches
        while (i < 1001) : (i += 1) {}
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_without_setEvalBranchQuota_builtin.zig:4:9: note: use @setEvalBranchQuota() to raise the branch limit from 1000

现在我们使用 @setEvalBranchQuota

test_setEvalBranchQuota_builtin.zig
test "foo" {
    comptime {
        @setEvalBranchQuota(1001);
        var i = 0;
        while (i < 1001) : (i += 1) {}
    }
}
Shell
$ zig test test_setEvalBranchQuota_builtin.zig
1/1 test_setEvalBranchQuota_builtin.test.foo...OK
All 1 tests passed.

另请参阅

@setFloatMode §

@setFloatMode(comptime mode: FloatMode) void

更改当前作用域中浮点运算的定义规则。

  • Strict (默认) - 浮点运算遵循严格的 IEEE 规范。
  • Optimized - 浮点运算可以执行以下所有操作:
    • 假设参数和结果不是 NaN。优化需要保持对 NaN 的合法行为,但结果值未定义。
    • 假设参数和结果不是 +/-Inf。优化需要保持对 +/-Inf 的合法行为,但结果值未定义。
    • 将零参数或结果的符号视为无关紧要。
    • 使用参数的倒数而不是执行除法。
    • 执行浮点收缩(例如,将乘法后跟加法融合为融合乘加)。
    • 执行可能在浮点运算中改变结果的代数等价转换(例如,重新关联)。
    这等同于 GCC 中的 -ffast-math

浮点模式由子作用域继承,并且可以在任何作用域中覆盖。您可以通过使用编译时块在结构体或模块作用域中设置浮点模式。

FloatMode 可以通过 @import("std").builtin.FloatMode 找到。

另请参阅

@setRuntimeSafety §

@setRuntimeSafety(comptime safety_on: bool) void

设置包含函数调用的作用域是否启用运行时安全检查。

test_setRuntimeSafety_builtin.zig
test "@setRuntimeSafety" {
    // The builtin applies to the scope that it is called in. So here, integer overflow
    // will not be caught in ReleaseFast and ReleaseSmall modes:
    // var x: u8 = 255;
    // x += 1; // Unchecked Illegal Behavior in ReleaseFast/ReleaseSmall modes.
    {
        // However this block has safety enabled, so safety checks happen here,
        // even in ReleaseFast and ReleaseSmall modes.
        @setRuntimeSafety(true);
        var x: u8 = 255;
        x += 1;

        {
            // The value can be overridden at any scope. So here integer overflow
            // would not be caught in any build mode.
            @setRuntimeSafety(false);
            // var x: u8 = 255;
            // x += 1; // Unchecked Illegal Behavior in all build modes.
        }
    }
}
Shell
$ zig test test_setRuntimeSafety_builtin.zig -OReleaseFast
1/1 test_setRuntimeSafety_builtin.test.@setRuntimeSafety...thread 210982 panic: integer overflow
/home/andy/dev/zig/doc/langref/test_setRuntimeSafety_builtin.zig:11:11: 0x100b138 in test.@setRuntimeSafety (test)
        x += 1;
          ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x1033268 in main (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10318fd in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x103140d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/5d6f3765262213cc9dd53d1c647e7bc5/test --seed=0xefe8119a

注意:计划将 @setRuntimeSafety 替换为 @optimizeFor

@shlExact §

@shlExact(value: T, shift_amt: Log2T) T

执行左移操作 (<<)。对于无符号整数,如果任何 1 位被移出,结果是未定义的。对于有符号整数,如果任何与结果符号位不一致的位被移出,结果是未定义的。

shift_amt 的类型是一个无符号整数,其位数为 log2(@typeInfo(T).int.bits)。这是因为 shift_amt >= @typeInfo(T).int.bits 会触发安全检查的非法行为

comptime_int 被建模为具有无限位数的整数,这意味着在这种情况下,@shlExact 总是产生结果,并且不会产生编译错误。

另请参阅

@shlWithOverflow §

@shlWithOverflow(a: anytype, shift_amt: Log2T) struct { @TypeOf(a), u1 }

执行 a << b 并返回一个包含结果和可能溢出位的元组。

shift_amt 的类型是一个无符号整数,其位数为 log2(@typeInfo(@TypeOf(a)).int.bits)。这是因为 shift_amt >= @typeInfo(@TypeOf(a)).int.bits 会触发安全检查的非法行为

另请参阅

@shrExact §

@shrExact(value: T, shift_amt: Log2T) T

执行右移操作 (>>)。调用者保证移位不会移出任何 1 位。

shift_amt 的类型是一个无符号整数,其位数为 log2(@typeInfo(T).int.bits)。这是因为 shift_amt >= @typeInfo(T).int.bits 会触发安全检查的非法行为

另请参阅

@shuffle §

@shuffle(comptime E: type, a: @Vector(a_len, E), b: @Vector(b_len, E), comptime mask: @Vector(mask_len, i32)) @Vector(mask_len, E)

通过根据 maskab 中选择元素来构造新的向量

mask 中的每个元素从 ab 中选择一个元素。正数从 a 开始选择,从 0 开始。负数从 b 开始选择,从 -1 开始递减。建议对来自 b 的索引使用 ~ 运算符,以便两个索引都可以从 0 开始(即 ~@as(i32, 0)-1)。

对于 mask 的每个元素,如果它或从 ab 中选择的值是 undefined,则结果元素是 undefined

a_lenb_len 的长度可能不同。mask 中超出范围的元素索引会导致编译错误。

如果 abundefined,则它等同于一个与另一个向量长度相同且所有元素都是 undefined 的向量。如果两个向量都是 undefined@shuffle 返回一个所有元素都是 undefined 的向量。

E 必须是整数浮点数指针bool。掩码可以有任何向量长度,其长度决定了结果长度。

test_shuffle_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "vector @shuffle" {
    const a = @Vector(7, u8){ 'o', 'l', 'h', 'e', 'r', 'z', 'w' };
    const b = @Vector(4, u8){ 'w', 'd', '!', 'x' };

    // To shuffle within a single vector, pass undefined as the second argument.
    // Notice that we can re-order, duplicate, or omit elements of the input vector
    const mask1 = @Vector(5, i32){ 2, 3, 1, 1, 0 };
    const res1: @Vector(5, u8) = @shuffle(u8, a, undefined, mask1);
    try expect(std.mem.eql(u8, &@as([5]u8, res1), "hello"));

    // Combining two vectors
    const mask2 = @Vector(6, i32){ -1, 0, 4, 1, -2, -3 };
    const res2: @Vector(6, u8) = @shuffle(u8, a, b, mask2);
    try expect(std.mem.eql(u8, &@as([6]u8, res2), "world!"));
}
Shell
$ zig test test_shuffle_builtin.zig
1/1 test_shuffle_builtin.test.vector @shuffle...OK
All 1 tests passed.

另请参阅

@sizeOf §

@sizeOf(comptime T: type) comptime_int

此函数返回在内存中存储 T 所需的字节数。结果是一个目标特定的编译时常量。

此大小可能包含填充字节。如果内存中有两个连续的 T,则填充将是索引 0 处的元素与索引 1 处的元素之间的字节偏移量。对于整数,请考虑是使用 @sizeOf(T) 还是 @typeInfo(T).int.bits

此函数在运行时测量大小。对于运行时不允许的类型,例如 comptime_inttype,结果为 0

另请参阅

@splat §

@splat(scalar: anytype) anytype

生成一个向量,其中每个元素都是 scalar 值。返回类型以及向量的长度是推断的。

test_splat_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "vector @splat" {
    const scalar: u32 = 5;
    const result: @Vector(4, u32) = @splat(scalar);
    try expect(std.mem.eql(u32, &@as([4]u32, result), &[_]u32{ 5, 5, 5, 5 }));
}
Shell
$ zig test test_splat_builtin.zig
1/1 test_splat_builtin.test.vector @splat...OK
All 1 tests passed.

scalar 必须是整数布尔值浮点数指针

另请参阅

@reduce §

@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E

通过使用指定运算符 op 对其元素执行顺序水平归约,将向量转换为标量值(类型为 E)。

并非所有运算符都适用于所有向量元素类型:

  • 每个运算符都适用于整数向量。
  • .And, .Or, .Xor 额外适用于 bool 向量,
  • .Min, .Max, .Add, .Mul 额外适用于浮点向量,

请注意,整数类型的 .Add.Mul 归约是环绕的;当应用于浮点类型时,操作关联性会保留,除非浮点模式设置为 Optimized

test_reduce_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "vector @reduce" {
    const V = @Vector(4, i32);
    const value = V{ 1, -1, 1, -1 };
    const result = value > @as(V, @splat(0));
    // result is { true, false, true, false };
    try comptime expect(@TypeOf(result) == @Vector(4, bool));
    const is_all_true = @reduce(.And, result);
    try comptime expect(@TypeOf(is_all_true) == bool);
    try expect(is_all_true == false);
}
Shell
$ zig test test_reduce_builtin.zig
1/1 test_reduce_builtin.test.vector @reduce...OK
All 1 tests passed.

另请参阅

@src §

@src() std.builtin.SourceLocation

返回一个 SourceLocation 结构体,表示函数的名称和在源代码中的位置。此函数必须在函数中调用。

test_src_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "@src" {
    try doTheTest();
}

fn doTheTest() !void {
    const src = @src();

    try expect(src.line == 9);
    try expect(src.column == 17);
    try expect(std.mem.endsWith(u8, src.fn_name, "doTheTest"));
    try expect(std.mem.endsWith(u8, src.file, "test_src_builtin.zig"));
}
Shell
$ zig test test_src_builtin.zig
1/1 test_src_builtin.test.@src...OK
All 1 tests passed.

@sqrt §

@sqrt(value: anytype) @TypeOf(value)

计算浮点数的平方根。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@sin §

@sin(value: anytype) @TypeOf(value)

以弧度为单位的浮点数正弦三角函数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@cos §

@cos(value: anytype) @TypeOf(value)

以弧度为单位的浮点数余弦三角函数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@tan §

@tan(value: anytype) @TypeOf(value)

以弧度为单位的浮点数正切三角函数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@exp §

@exp(value: anytype) @TypeOf(value)

浮点数的自然对数指数函数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@exp2 §

@exp2(value: anytype) @TypeOf(value)

浮点数的以 2 为底的指数函数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@log §

@log(value: anytype) @TypeOf(value)

返回浮点数的自然对数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@log2 §

@log2(value: anytype) @TypeOf(value)

返回浮点数的以 2 为底的对数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@log10 §

@log10(value: anytype) @TypeOf(value)

返回浮点数的以 10 为底的对数。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@abs §

@abs(value: anytype) anytype

返回整数或浮点数的绝对值。如果可用,使用专用硬件指令。如果操作数是整数,返回类型始终是与操作数位宽相同的无符号整数。支持无符号整数操作数。对于有符号整数操作数,此内置函数不会溢出。

支持浮点数整数以及浮点数或整数的向量

@floor §

@floor(value: anytype) @TypeOf(value)

返回不大于给定浮点数的最大整数值。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@ceil §

@ceil(value: anytype) @TypeOf(value)

返回不小于给定浮点数的最小整数值。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@trunc §

@trunc(value: anytype) @TypeOf(value)

将给定浮点数舍入为整数,朝零方向。如果可用,使用专用硬件指令。

支持浮点数和浮点数的向量

@round §

@round(value: anytype) @TypeOf(value)

将给定浮点数舍入到最近的整数。如果两个整数同样接近,则舍入到远离零的方向。如果可用,使用专用硬件指令。

test_round_builtin.zig
const expect = @import("std").testing.expect;

test "@round" {
    try expect(@round(1.4) == 1);
    try expect(@round(1.5) == 2);
    try expect(@round(-1.4) == -1);
    try expect(@round(-2.5) == -3);
}
Shell
$ zig test test_round_builtin.zig
1/1 test_round_builtin.test.@round...OK
All 1 tests passed.

支持浮点数和浮点数的向量

@subWithOverflow §

@subWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }

执行 a - b 并返回一个包含结果和可能溢出位的元组。

@tagName §

@tagName(value: anytype) [:0]const u8

将枚举值或联合体值转换为表示名称的字符串字面量。

如果枚举是非穷尽的且标签值未映射到名称,则会触发安全检查的非法行为

@This §

@This() type

返回此函数调用所在的最近的结构体、枚举或联合体。这对于需要引用自身的匿名结构体可能很有用:

test_this_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "@This()" {
    var items = [_]i32{ 1, 2, 3, 4 };
    const list = List(i32){ .items = items[0..] };
    try expect(list.length() == 4);
}

fn List(comptime T: type) type {
    return struct {
        const Self = @This();

        items: []T,

        fn length(self: Self) usize {
            return self.items.len;
        }
    };
}
Shell
$ zig test test_this_builtin.zig
1/1 test_this_builtin.test.@This()...OK
All 1 tests passed.

当在文件作用域中使用 @This() 时,它返回对与当前文件对应的结构体的引用。

@trap §

@trap() noreturn

此函数插入一个平台特定的陷阱/中断指令,可用于异常退出程序。这可以通过显式发出可能导致某种非法指令异常的无效指令来实现。与 @breakpoint() 不同,执行在此点之后不会继续。

在函数作用域之外,此内置函数会导致编译错误。

另请参阅

@truncate §

@truncate(integer: anytype) anytype

此函数从整数类型截断位,从而得到一个更小或相同大小的整数类型。返回类型是推断的结果类型。

此函数总是截断整数的有效位,无论目标平台上的字节序如何。

对超出目标类型范围的数字调用 @truncate 是良好定义且可工作的代码:

test_truncate_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "integer truncation" {
    const a: u16 = 0xabcd;
    const b: u8 = @truncate(a);
    try expect(b == 0xcd);
}
Shell
$ zig test test_truncate_builtin.zig
1/1 test_truncate_builtin.test.integer truncation...OK
All 1 tests passed.

使用@intCast转换保证适合目标类型的数字。

@Type §

@Type(comptime info: std.builtin.Type) type

此函数是@typeInfo的逆操作。它将类型信息具体化为 type

它适用于以下类型:

@typeInfo §

@typeInfo(comptime T: type) std.builtin.Type

提供类型反射。

结构体联合体枚举错误集的类型信息具有保证与源文件中出现顺序相同的字段。

结构体联合体枚举不透明类型的类型信息具有声明,这些声明也保证与源文件中出现顺序相同。

@typeName §

@typeName(T: type) *const [N:0]u8

此函数返回类型的字符串表示形式,作为数组。它等同于类型名称的字符串字面量。返回的类型名称是完全限定的,父命名空间作为类型名称的一部分,以一系列点分隔。

@TypeOf §

@TypeOf(...) type

@TypeOf 是一个特殊的内置函数,它接受任意(非零)数量的表达式作为参数,并使用对等类型解析返回结果的类型。

表达式会被评估,但它们保证没有*运行时*副作用:

test_TypeOf_builtin.zig
const std = @import("std");
const expect = std.testing.expect;

test "no runtime side effects" {
    var data: i32 = 0;
    const T = @TypeOf(foo(i32, &data));
    try comptime expect(T == i32);
    try expect(data == 0);
}

fn foo(comptime T: type, ptr: *T) T {
    ptr.* += 1;
    return ptr.*;
}
Shell
$ zig test test_TypeOf_builtin.zig
1/1 test_TypeOf_builtin.test.no runtime side effects...OK
All 1 tests passed.

@unionInit §

@unionInit(comptime Union: type, comptime active_field_name: []const u8, init_expr) Union

这与联合体初始化语法相同,只是字段名是一个编译期已知的值而不是标识符标记。

@unionInit 将其结果位置转发给 init_expr

@Vector §

@Vector(len: comptime_int, Element: type) type

创建向量

@volatileCast §

@volatileCast(value: anytype) DestType

从指针中移除 volatile 限定符。

@workGroupId §

@workGroupId(comptime dimension: u32) u32

返回当前内核调用中 dimension 维度的工作组索引。

@workGroupSize §

@workGroupSize(comptime dimension: u32) u32

返回一个工作组在 dimension 维度中拥有的工作项数量。

@workItemId §

@workItemId(comptime dimension: u32) u32

返回工作组中 dimension 维度的最小工作项索引。此函数返回 0(包含)到 @workGroupSize(dimension)(不包含)之间的值。

构建模式 §

Zig 有四种构建模式:

build.zig 文件添加标准构建选项

build.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const optimize = b.standardOptimizeOption(.{});
    const exe = b.addExecutable(.{
        .name = "example",
        .root_source_file = b.path("example.zig"),
        .optimize = optimize,
    });
    b.default_step.dependOn(&exe.step);
}

这导致以下选项可用:

-Doptimize=Debug
优化关闭,安全检查开启(默认)
-Doptimize=ReleaseSafe
优化开启,安全检查开启
-Doptimize=ReleaseFast
优化开启,安全检查关闭
-Doptimize=ReleaseSmall
大小优化开启,安全检查关闭

调试模式 §

Shell
$ zig build-exe example.zig
  • 编译速度快
  • 安全检查已启用
  • 运行时性能慢
  • 二进制文件大
  • 没有可重现构建要求

ReleaseFast §

Shell
$ zig build-exe example.zig -O ReleaseFast
  • 运行时性能快
  • 安全检查已禁用
  • 编译速度慢
  • 二进制文件大
  • 可重现构建

ReleaseSafe §

Shell
$ zig build-exe example.zig -O ReleaseSafe
  • 中等运行时性能
  • 安全检查已启用
  • 编译速度慢
  • 二进制文件大
  • 可重现构建

ReleaseSmall §

Shell
$ zig build-exe example.zig -O ReleaseSmall
  • 中等运行时性能
  • 安全检查已禁用
  • 编译速度慢
  • 二进制文件小
  • 可重现构建

另请参阅

单线程构建 §

Zig 有一个编译选项 -fsingle-threaded,它具有以下效果:

  • 所有线程局部变量都被视为常规的容器级变量
  • 异步函数的开销变得等同于函数调用开销。
  • @import("builtin").single_threaded 变为 true,因此读取此变量的各种用户态 API 变得更高效。例如,std.Mutex 变成一个空数据结构,其所有函数都变成空操作。

非法行为 §

Zig 中的许多操作会触发所谓的“非法行为”(IB)。如果在编译时检测到非法行为,Zig 会发出编译错误并拒绝继续。否则,当非法行为未在编译时捕获时,它分为两类。

一些非法行为是*安全检查*的:这意味着编译器将在运行时可能发生非法行为的任何地方插入“安全检查”,以确定它是否即将发生。如果是,安全检查“失败”,这将触发 panic。

所有其他非法行为都是*未检查*的,这意味着编译器无法为其插入安全检查。如果在运行时调用未检查的非法行为,任何事情都可能发生:通常是某种崩溃,但优化器可以自由地使未检查的非法行为执行任何操作,例如调用任意函数或破坏任意数据。这类似于其他一些语言中“未定义行为”的概念。请注意,如果在编译时评估,未检查的非法行为仍然总是导致编译错误,因为 Zig 编译器能够在编译时执行比运行时更复杂的检查。

大多数非法行为是经过安全检查的。然而,为了方便优化,在ReleaseFastReleaseSmall优化模式下,安全检查默认是禁用的。安全检查也可以使用@setRuntimeSafety按块启用或禁用,覆盖当前优化模式的默认设置。当安全检查禁用时,安全检查的非法行为表现得像未检查的非法行为;也就是说,任何行为都可能因调用它而产生。

当安全检查失败时,Zig 的默认 panic 处理程序会以堆栈跟踪崩溃,如下所示:

test_illegal_behavior.zig
test "safety check" {
    unreachable;
}
Shell
$ zig test test_illegal_behavior.zig
1/1 test_illegal_behavior.test.safety check...thread 210495 panic: reached unreachable code
/home/andy/dev/zig/doc/langref/test_illegal_behavior.zig:2:5: 0x10488c8 in test.safety check (test)
    unreachable;
    ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:214:25: 0x10ef065 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/dev/zig/lib/compiler/test_runner.zig:62:28: 0x10e744d in main (test)
        return mainTerminal();
                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e68c2 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e649d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/dev/zig/.zig-cache/o/532079bf373c937f8f95a990d9470d7c/test --seed=0xbaefa3c8

达到不可达代码 §

编译时

test_comptime_reaching_unreachable.zig
comptime {
    assert(false);
}
fn assert(ok: bool) void {
    if (!ok) unreachable; // assertion failure
}
Shell
$ zig test test_comptime_reaching_unreachable.zig
/home/andy/dev/zig/doc/langref/test_comptime_reaching_unreachable.zig:5:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_comptime_reaching_unreachable.zig:2:11: note: called from here
    assert(false);
    ~~~~~~^~~~~~~

运行时

runtime_reaching_unreachable.zig
const std = @import("std");

pub fn main() void {
    std.debug.assert(false);
}
Shell
$ zig build-exe runtime_reaching_unreachable.zig
$ ./runtime_reaching_unreachable
thread 201175 panic: reached unreachable code
/home/andy/dev/zig/lib/std/debug.zig:550:14: 0x10489fd in assert (runtime_reaching_unreachable)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/dev/zig/doc/langref/runtime_reaching_unreachable.zig:4:21: 0x10de84a in main (runtime_reaching_unreachable)
    std.debug.assert(false);
                    ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de242 in posixCallMainAndExit (runtime_reaching_unreachable)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10dde1d in _start (runtime_reaching_unreachable)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

索引超出边界 §

编译时

test_comptime_index_out_of_bounds.zig
comptime {
    const array: [5]u8 = "hello".*;
    const garbage = array[5];
    _ = garbage;
}
Shell
$ zig test test_comptime_index_out_of_bounds.zig
/home/andy/dev/zig/doc/langref/test_comptime_index_out_of_bounds.zig:3:27: error: index 5 outside array of length 5
    const garbage = array[5];
                          ^

运行时

runtime_index_out_of_bounds.zig
pub fn main() void {
    const x = foo("hello");
    _ = x;
}

fn foo(x: []const u8) u8 {
    return x[5];
}
Shell
$ zig build-exe runtime_index_out_of_bounds.zig
$ ./runtime_index_out_of_bounds
thread 207520 panic: index out of bounds: index 5, len 5
/home/andy/dev/zig/doc/langref/runtime_index_out_of_bounds.zig:7:13: 0x10df121 in foo (runtime_index_out_of_bounds)
    return x[5];
            ^
/home/andy/dev/zig/doc/langref/runtime_index_out_of_bounds.zig:2:18: 0x10de886 in main (runtime_index_out_of_bounds)
    const x = foo("hello");
                 ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de272 in posixCallMainAndExit (runtime_index_out_of_bounds)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10dde4d in _start (runtime_index_out_of_bounds)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

将负数转换为无符号整数 §

编译时

test_comptime_invalid_cast.zig
comptime {
    const value: i32 = -1;
    const unsigned: u32 = @intCast(value);
    _ = unsigned;
}
Shell
$ zig test test_comptime_invalid_cast.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_cast.zig:3:36: error: type 'u32' cannot represent integer value '-1'
    const unsigned: u32 = @intCast(value);
                                   ^~~~~

运行时

runtime_invalid_cast.zig
const std = @import("std");

pub fn main() void {
    var value: i32 = -1; // runtime-known
    _ = &value;
    const unsigned: u32 = @intCast(value);
    std.debug.print("value: {}\n", .{unsigned});
}
Shell
$ zig build-exe runtime_invalid_cast.zig
$ ./runtime_invalid_cast
thread 202040 panic: attempt to cast negative value to unsigned integer
/home/andy/dev/zig/doc/langref/runtime_invalid_cast.zig:6:27: 0x10de986 in main (runtime_invalid_cast)
    const unsigned: u32 = @intCast(value);
                          ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de362 in posixCallMainAndExit (runtime_invalid_cast)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf3d in _start (runtime_invalid_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

要获取无符号整数的最大值,请使用 std.math.maxInt

强制转换截断数据 §

编译时

test_comptime_invalid_cast_truncate.zig
comptime {
    const spartan_count: u16 = 300;
    const byte: u8 = @intCast(spartan_count);
    _ = byte;
}
Shell
$ zig test test_comptime_invalid_cast_truncate.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_cast_truncate.zig:3:31: error: type 'u8' cannot represent integer value '300'
    const byte: u8 = @intCast(spartan_count);
                              ^~~~~~~~~~~~~

运行时

runtime_invalid_cast_truncate.zig
const std = @import("std");

pub fn main() void {
    var spartan_count: u16 = 300; // runtime-known
    _ = &spartan_count;
    const byte: u8 = @intCast(spartan_count);
    std.debug.print("value: {}\n", .{byte});
}
Shell
$ zig build-exe runtime_invalid_cast_truncate.zig
$ ./runtime_invalid_cast_truncate
thread 197892 panic: integer cast truncated bits
/home/andy/dev/zig/doc/langref/runtime_invalid_cast_truncate.zig:6:22: 0x10dea18 in main (runtime_invalid_cast_truncate)
    const byte: u8 = @intCast(spartan_count);
                     ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de3f2 in posixCallMainAndExit (runtime_invalid_cast_truncate)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfcd in _start (runtime_invalid_cast_truncate)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

要截断位,请使用@truncate

整数溢出 §

默认操作 §

以下运算符可能导致整数溢出:

编译时加法示例:

test_comptime_overflow.zig
comptime {
    var byte: u8 = 255;
    byte += 1;
}
Shell
$ zig test test_comptime_overflow.zig
/home/andy/dev/zig/doc/langref/test_comptime_overflow.zig:3:10: error: overflow of integer type 'u8' with value '256'
    byte += 1;
    ~~~~~^~~~

运行时

runtime_overflow.zig
const std = @import("std");

pub fn main() void {
    var byte: u8 = 255;
    byte += 1;
    std.debug.print("value: {}\n", .{byte});
}
Shell
$ zig build-exe runtime_overflow.zig
$ ./runtime_overflow
thread 208546 panic: integer overflow
/home/andy/dev/zig/doc/langref/runtime_overflow.zig:5:10: 0x10dea19 in main (runtime_overflow)
    byte += 1;
         ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de3f2 in posixCallMainAndExit (runtime_overflow)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfcd in _start (runtime_overflow)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

标准库数学函数 §

标准库提供的这些函数可能返回错误。

  • @import("std").math.add
  • @import("std").math.sub
  • @import("std").math.mul
  • @import("std").math.divTrunc
  • @import("std").math.divFloor
  • @import("std").math.divExact
  • @import("std").math.shl

捕获加法溢出的示例:

math_add.zig
const math = @import("std").math;
const print = @import("std").debug.print;
pub fn main() !void {
    var byte: u8 = 255;

    byte = if (math.add(u8, byte, 1)) |result| result else |err| {
        print("unable to add one: {s}\n", .{@errorName(err)});
        return err;
    };

    print("result: {}\n", .{byte});
}
Shell
$ zig build-exe math_add.zig
$ ./math_add
unable to add one: Overflow
error: Overflow
/home/andy/dev/zig/lib/std/math.zig:565:21: 0x10dea35 in add__anon_24027 (math_add)
    if (ov[1] != 0) return error.Overflow;
                    ^
/home/andy/dev/zig/doc/langref/math_add.zig:8:9: 0x10de9cb in main (math_add)
        return err;
        ^

内置溢出函数 §

这些内置函数返回一个元组,其中包含是否发生溢出(作为 u1)以及操作可能溢出的位

@addWithOverflow 的示例

addWithOverflow_builtin.zig
const print = @import("std").debug.print;
pub fn main() void {
    const byte: u8 = 255;

    const ov = @addWithOverflow(byte, 10);
    if (ov[1] != 0) {
        print("overflowed result: {}\n", .{ov[0]});
    } else {
        print("result: {}\n", .{ov[0]});
    }
}
Shell
$ zig build-exe addWithOverflow_builtin.zig
$ ./addWithOverflow_builtin
overflowed result: 9

环绕操作 §

这些操作保证具有环绕语义。

  • +% (环绕加法)
  • -% (环绕减法)
  • -% (环绕取反)
  • *% (环绕乘法)
test_wraparound_semantics.zig
const std = @import("std");
const expect = std.testing.expect;
const minInt = std.math.minInt;
const maxInt = std.math.maxInt;

test "wraparound addition and subtraction" {
    const x: i32 = maxInt(i32);
    const min_val = x +% 1;
    try expect(min_val == minInt(i32));
    const max_val = min_val -% 1;
    try expect(max_val == maxInt(i32));
}
Shell
$ zig test test_wraparound_semantics.zig
1/1 test_wraparound_semantics.test.wraparound addition and subtraction...OK
All 1 tests passed.

精确左移溢出 §

编译时

test_comptime_shlExact_overwlow.zig
comptime {
    const x = @shlExact(@as(u8, 0b01010101), 2);
    _ = x;
}
Shell
$ zig test test_comptime_shlExact_overwlow.zig
/home/andy/dev/zig/doc/langref/test_comptime_shlExact_overwlow.zig:2:15: error: operation caused overflow
    const x = @shlExact(@as(u8, 0b01010101), 2);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

运行时

runtime_shlExact_overflow.zig
const std = @import("std");

pub fn main() void {
    var x: u8 = 0b01010101; // runtime-known
    _ = &x;
    const y = @shlExact(x, 2);
    std.debug.print("value: {}\n", .{y});
}
Shell
$ zig build-exe runtime_shlExact_overflow.zig
$ ./runtime_shlExact_overflow
thread 197929 panic: left shift overflowed bits
/home/andy/dev/zig/doc/langref/runtime_shlExact_overflow.zig:6:5: 0x10deab1 in main (runtime_shlExact_overflow)
    const y = @shlExact(x, 2);
    ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de462 in posixCallMainAndExit (runtime_shlExact_overflow)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10de03d in _start (runtime_shlExact_overflow)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

精确右移溢出 §

编译时

test_comptime_shrExact_overflow.zig
comptime {
    const x = @shrExact(@as(u8, 0b10101010), 2);
    _ = x;
}
Shell
$ zig test test_comptime_shrExact_overflow.zig
/home/andy/dev/zig/doc/langref/test_comptime_shrExact_overflow.zig:2:15: error: exact shift shifted out 1 bits
    const x = @shrExact(@as(u8, 0b10101010), 2);
              ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

运行时

runtime_shrExact_overflow.zig
const std = @import("std");

pub fn main() void {
    var x: u8 = 0b10101010; // runtime-known
    _ = &x;
    const y = @shrExact(x, 2);
    std.debug.print("value: {}\n", .{y});
}
Shell
$ zig build-exe runtime_shrExact_overflow.zig
$ ./runtime_shrExact_overflow
thread 205633 panic: right shift overflowed bits
/home/andy/dev/zig/doc/langref/runtime_shrExact_overflow.zig:6:5: 0x10deaad in main (runtime_shrExact_overflow)
    const y = @shrExact(x, 2);
    ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de462 in posixCallMainAndExit (runtime_shrExact_overflow)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10de03d in _start (runtime_shrExact_overflow)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

除以零 §

编译时

test_comptime_division_by_zero.zig
comptime {
    const a: i32 = 1;
    const b: i32 = 0;
    const c = a / b;
    _ = c;
}
Shell
$ zig test test_comptime_division_by_zero.zig
/home/andy/dev/zig/doc/langref/test_comptime_division_by_zero.zig:4:19: error: division by zero here causes undefined behavior
    const c = a / b;
                  ^

运行时

runtime_division_by_zero.zig
const std = @import("std");

pub fn main() void {
    var a: u32 = 1;
    var b: u32 = 0;
    _ = .{ &a, &b };
    const c = a / b;
    std.debug.print("value: {}\n", .{c});
}
Shell
$ zig build-exe runtime_division_by_zero.zig
$ ./runtime_division_by_zero
thread 210516 panic: division by zero
/home/andy/dev/zig/doc/langref/runtime_division_by_zero.zig:7:17: 0x10de9ca in main (runtime_division_by_zero)
    const c = a / b;
                ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de372 in posixCallMainAndExit (runtime_division_by_zero)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf4d in _start (runtime_division_by_zero)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

余数除以零 §

编译时

test_comptime_remainder_division_by_zero.zig
comptime {
    const a: i32 = 10;
    const b: i32 = 0;
    const c = a % b;
    _ = c;
}
Shell
$ zig test test_comptime_remainder_division_by_zero.zig
/home/andy/dev/zig/doc/langref/test_comptime_remainder_division_by_zero.zig:4:19: error: division by zero here causes undefined behavior
    const c = a % b;
                  ^

运行时

runtime_remainder_division_by_zero.zig
const std = @import("std");

pub fn main() void {
    var a: u32 = 10;
    var b: u32 = 0;
    _ = .{ &a, &b };
    const c = a % b;
    std.debug.print("value: {}\n", .{c});
}
Shell
$ zig build-exe runtime_remainder_division_by_zero.zig
$ ./runtime_remainder_division_by_zero
thread 204394 panic: division by zero
/home/andy/dev/zig/doc/langref/runtime_remainder_division_by_zero.zig:7:17: 0x10de9ca in main (runtime_remainder_division_by_zero)
    const c = a % b;
                ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de372 in posixCallMainAndExit (runtime_remainder_division_by_zero)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf4d in _start (runtime_remainder_division_by_zero)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

精确除法余数 §

编译时

test_comptime_divExact_remainder.zig
comptime {
    const a: u32 = 10;
    const b: u32 = 3;
    const c = @divExact(a, b);
    _ = c;
}
Shell
$ zig test test_comptime_divExact_remainder.zig
/home/andy/dev/zig/doc/langref/test_comptime_divExact_remainder.zig:4:15: error: exact division produced remainder
    const c = @divExact(a, b);
              ^~~~~~~~~~~~~~~

运行时

runtime_divExact_remainder.zig
const std = @import("std");

pub fn main() void {
    var a: u32 = 10;
    var b: u32 = 3;
    _ = .{ &a, &b };
    const c = @divExact(a, b);
    std.debug.print("value: {}\n", .{c});
}
Shell
$ zig build-exe runtime_divExact_remainder.zig
$ ./runtime_divExact_remainder
thread 206096 panic: exact division produced remainder
/home/andy/dev/zig/doc/langref/runtime_divExact_remainder.zig:7:15: 0x10de9eb in main (runtime_divExact_remainder)
    const c = @divExact(a, b);
              ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de372 in posixCallMainAndExit (runtime_divExact_remainder)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf4d in _start (runtime_divExact_remainder)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

尝试解包 Null §

编译时

test_comptime_unwrap_null.zig
comptime {
    const optional_number: ?i32 = null;
    const number = optional_number.?;
    _ = number;
}
Shell
$ zig test test_comptime_unwrap_null.zig
/home/andy/dev/zig/doc/langref/test_comptime_unwrap_null.zig:3:35: error: unable to unwrap null
    const number = optional_number.?;
                   ~~~~~~~~~~~~~~~^~

运行时

runtime_unwrap_null.zig
const std = @import("std");

pub fn main() void {
    var optional_number: ?i32 = null;
    _ = &optional_number;
    const number = optional_number.?;
    std.debug.print("value: {}\n", .{number});
}
Shell
$ zig build-exe runtime_unwrap_null.zig
$ ./runtime_unwrap_null
thread 210622 panic: attempt to use null value
/home/andy/dev/zig/doc/langref/runtime_unwrap_null.zig:6:35: 0x10de9b6 in main (runtime_unwrap_null)
    const number = optional_number.?;
                                  ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de372 in posixCallMainAndExit (runtime_unwrap_null)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddf4d in _start (runtime_unwrap_null)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

避免此崩溃的一种方法是使用 if 表达式检查是否为 null,而不是假设非 null:

testing_null_with_if.zig
const print = @import("std").debug.print;
pub fn main() void {
    const optional_number: ?i32 = null;

    if (optional_number) |number| {
        print("got number: {}\n", .{number});
    } else {
        print("it's null\n", .{});
    }
}
Shell
$ zig build-exe testing_null_with_if.zig
$ ./testing_null_with_if
it's null

另请参阅

尝试解包错误 §

编译时

test_comptime_unwrap_error.zig
comptime {
    const number = getNumberOrFail() catch unreachable;
    _ = number;
}

fn getNumberOrFail() !i32 {
    return error.UnableToReturnNumber;
}
Shell
$ zig test test_comptime_unwrap_error.zig
/home/andy/dev/zig/doc/langref/test_comptime_unwrap_error.zig:2:44: error: caught unexpected error 'UnableToReturnNumber'
    const number = getNumberOrFail() catch unreachable;
                                           ^~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_comptime_unwrap_error.zig:7:18: note: error returned here
    return error.UnableToReturnNumber;
                 ^~~~~~~~~~~~~~~~~~~~

运行时

runtime_unwrap_error.zig
const std = @import("std");

pub fn main() void {
    const number = getNumberOrFail() catch unreachable;
    std.debug.print("value: {}\n", .{number});
}

fn getNumberOrFail() !i32 {
    return error.UnableToReturnNumber;
}
Shell
$ zig build-exe runtime_unwrap_error.zig
$ ./runtime_unwrap_error
thread 198035 panic: attempt to unwrap error: UnableToReturnNumber
/home/andy/dev/zig/doc/langref/runtime_unwrap_error.zig:9:5: 0x10df28f in getNumberOrFail (runtime_unwrap_error)
    return error.UnableToReturnNumber;
    ^
/home/andy/dev/zig/doc/langref/runtime_unwrap_error.zig:4:44: 0x10dea21 in main (runtime_unwrap_error)
    const number = getNumberOrFail() catch unreachable;
                                           ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de3d2 in posixCallMainAndExit (runtime_unwrap_error)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfad in _start (runtime_unwrap_error)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

避免此崩溃的一种方法是使用 if 表达式检查是否为错误,而不是假设结果成功:

testing_error_with_if.zig
const print = @import("std").debug.print;

pub fn main() void {
    const result = getNumberOrFail();

    if (result) |number| {
        print("got number: {}\n", .{number});
    } else |err| {
        print("got error: {s}\n", .{@errorName(err)});
    }
}

fn getNumberOrFail() !i32 {
    return error.UnableToReturnNumber;
}
Shell
$ zig build-exe testing_error_with_if.zig
$ ./testing_error_with_if
got error: UnableToReturnNumber

另请参阅

无效错误代码 §

编译时

test_comptime_invalid_error_code.zig
comptime {
    const err = error.AnError;
    const number = @intFromError(err) + 10;
    const invalid_err = @errorFromInt(number);
    _ = invalid_err;
}
Shell
$ zig test test_comptime_invalid_error_code.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_error_code.zig:4:39: error: integer value '11' represents no error
    const invalid_err = @errorFromInt(number);
                                      ^~~~~~

运行时

runtime_invalid_error_code.zig
const std = @import("std");

pub fn main() void {
    const err = error.AnError;
    var number = @intFromError(err) + 500;
    _ = &number;
    const invalid_err = @errorFromInt(number);
    std.debug.print("value: {}\n", .{invalid_err});
}
Shell
$ zig build-exe runtime_invalid_error_code.zig
$ ./runtime_invalid_error_code
thread 209716 panic: invalid error code
/home/andy/dev/zig/doc/langref/runtime_invalid_error_code.zig:7:5: 0x10dea46 in main (runtime_invalid_error_code)
    const invalid_err = @errorFromInt(number);
    ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de3d2 in posixCallMainAndExit (runtime_invalid_error_code)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfad in _start (runtime_invalid_error_code)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

无效枚举转换 §

编译时

test_comptime_invalid_enum_cast.zig
const Foo = enum {
    a,
    b,
    c,
};
comptime {
    const a: u2 = 3;
    const b: Foo = @enumFromInt(a);
    _ = b;
}
Shell
$ zig test test_comptime_invalid_enum_cast.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_enum_cast.zig:8:20: error: enum 'test_comptime_invalid_enum_cast.Foo' has no tag with value '3'
    const b: Foo = @enumFromInt(a);
                   ^~~~~~~~~~~~~~~
/home/andy/dev/zig/doc/langref/test_comptime_invalid_enum_cast.zig:1:13: note: enum declared here
const Foo = enum {
            ^~~~

运行时

runtime_invalid_enum_cast.zig
const std = @import("std");

const Foo = enum {
    a,
    b,
    c,
};

pub fn main() void {
    var a: u2 = 3;
    _ = &a;
    const b: Foo = @enumFromInt(a);
    std.debug.print("value: {s}\n", .{@tagName(b)});
}
Shell
$ zig build-exe runtime_invalid_enum_cast.zig
$ ./runtime_invalid_enum_cast
thread 210733 panic: invalid enum value
/home/andy/dev/zig/doc/langref/runtime_invalid_enum_cast.zig:12:20: 0x10dea0a in main (runtime_invalid_enum_cast)
    const b: Foo = @enumFromInt(a);
                   ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de3e2 in posixCallMainAndExit (runtime_invalid_enum_cast)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfbd in _start (runtime_invalid_enum_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

无效错误集转换 §

编译时

test_comptime_invalid_error_set_cast.zig
const Set1 = error{
    A,
    B,
};
const Set2 = error{
    A,
    C,
};
comptime {
    _ = @as(Set2, @errorCast(Set1.B));
}
Shell
$ zig test test_comptime_invalid_error_set_cast.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_error_set_cast.zig:10:19: error: 'error.B' not a member of error set 'error{A,C}'
    _ = @as(Set2, @errorCast(Set1.B));
                  ^~~~~~~~~~~~~~~~~~

运行时

runtime_invalid_error_set_cast.zig
const std = @import("std");

const Set1 = error{
    A,
    B,
};
const Set2 = error{
    A,
    C,
};
pub fn main() void {
    foo(Set1.B);
}
fn foo(set1: Set1) void {
    const x: Set2 = @errorCast(set1);
    std.debug.print("value: {}\n", .{x});
}
Shell
$ zig build-exe runtime_invalid_error_set_cast.zig
$ ./runtime_invalid_error_set_cast
thread 209635 panic: invalid error code
/home/andy/dev/zig/doc/langref/runtime_invalid_error_set_cast.zig:15:21: 0x10df2fd in foo (runtime_invalid_error_set_cast)
    const x: Set2 = @errorCast(set1);
                    ^
/home/andy/dev/zig/doc/langref/runtime_invalid_error_set_cast.zig:12:8: 0x10dea2c in main (runtime_invalid_error_set_cast)
    foo(Set1.B);
       ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de412 in posixCallMainAndExit (runtime_invalid_error_set_cast)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10ddfed in _start (runtime_invalid_error_set_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

不正确的指针对齐 §

编译时

test_comptime_incorrect_pointer_alignment.zig
comptime {
    const ptr: *align(1) i32 = @ptrFromInt(0x1);
    const aligned: *align(4) i32 = @alignCast(ptr);
    _ = aligned;
}
Shell
$ zig test test_comptime_incorrect_pointer_alignment.zig
/home/andy/dev/zig/doc/langref/test_comptime_incorrect_pointer_alignment.zig:3:47: error: pointer address 0x1 is not aligned to 4 bytes
    const aligned: *align(4) i32 = @alignCast(ptr);
                                              ^~~

运行时

runtime_incorrect_pointer_alignment.zig
const mem = @import("std").mem;
pub fn main() !void {
    var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
    const bytes = mem.sliceAsBytes(array[0..]);
    if (foo(bytes) != 0x11111111) return error.Wrong;
}
fn foo(bytes: []u8) u32 {
    const slice4 = bytes[1..5];
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
    return int_slice[0];
}
Shell
$ zig build-exe runtime_incorrect_pointer_alignment.zig
$ ./runtime_incorrect_pointer_alignment
thread 210771 panic: incorrect alignment
/home/andy/dev/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:9:64: 0x10de7d2 in foo (runtime_incorrect_pointer_alignment)
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                               ^
/home/andy/dev/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:5:12: 0x10de6cf in main (runtime_incorrect_pointer_alignment)
    if (foo(bytes) != 0x11111111) return error.Wrong;
           ^
/home/andy/dev/zig/lib/std/start.zig:660:37: 0x10de5ba in posixCallMainAndExit (runtime_incorrect_pointer_alignment)
            const result = root.main() catch |err| {
                                    ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10de16d in _start (runtime_incorrect_pointer_alignment)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

错误联合体字段访问 §

编译时

test_comptime_wrong_union_field_access.zig
comptime {
    var f = Foo{ .int = 42 };
    f.float = 12.34;
}

const Foo = union {
    float: f32,
    int: u32,
};
Shell
$ zig test test_comptime_wrong_union_field_access.zig
/home/andy/dev/zig/doc/langref/test_comptime_wrong_union_field_access.zig:3:6: error: access of union field 'float' while field 'int' is active
    f.float = 12.34;
    ~^~~~~~
/home/andy/dev/zig/doc/langref/test_comptime_wrong_union_field_access.zig:6:13: note: union declared here
const Foo = union {
            ^~~~~

运行时

runtime_wrong_union_field_access.zig
const std = @import("std");

const Foo = union {
    float: f32,
    int: u32,
};

pub fn main() void {
    var f = Foo{ .int = 42 };
    bar(&f);
}

fn bar(f: *Foo) void {
    f.float = 12.34;
    std.debug.print("value: {}\n", .{f.float});
}
Shell
$ zig build-exe runtime_wrong_union_field_access.zig
$ ./runtime_wrong_union_field_access
thread 202573 panic: access of union field 'float' while field 'int' is active
/home/andy/dev/zig/doc/langref/runtime_wrong_union_field_access.zig:14:6: 0x10e4b18 in bar (runtime_wrong_union_field_access)
    f.float = 12.34;
     ^
/home/andy/dev/zig/doc/langref/runtime_wrong_union_field_access.zig:10:8: 0x10e426c in main (runtime_wrong_union_field_access)
    bar(&f);
       ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10e3c52 in posixCallMainAndExit (runtime_wrong_union_field_access)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10e382d in _start (runtime_wrong_union_field_access)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

此安全性不适用于 externpacked 联合体。

要更改联合体的活跃字段,请像这样赋值整个联合体

change_active_union_field.zig
const std = @import("std");

const Foo = union {
    float: f32,
    int: u32,
};

pub fn main() void {
    var f = Foo{ .int = 42 };
    bar(&f);
}

fn bar(f: *Foo) void {
    f.* = Foo{ .float = 12.34 };
    std.debug.print("value: {}\n", .{f.float});
}
Shell
$ zig build-exe change_active_union_field.zig
$ ./change_active_union_field
value: 1.234e1

当字段的有效值未知时,要更改联合体的活跃字段,请使用 undefined,例如

undefined_active_union_field.zig
const std = @import("std");

const Foo = union {
    float: f32,
    int: u32,
};

pub fn main() void {
    var f = Foo{ .int = 42 };
    f = Foo{ .float = undefined };
    bar(&f);
    std.debug.print("value: {}\n", .{f.float});
}

fn bar(f: *Foo) void {
    f.float = 12.34;
}
Shell
$ zig build-exe undefined_active_union_field.zig
$ ./undefined_active_union_field
value: 1.234e1

另请参阅

浮点数到整数的越界转换 §

当浮点数的值超出整数类型的范围时,将其转换为整数时会发生此情况。

编译时

test_comptime_out_of_bounds_float_to_integer_cast.zig
comptime {
    const float: f32 = 4294967296;
    const int: i32 = @intFromFloat(float);
    _ = int;
}
Shell
$ zig test test_comptime_out_of_bounds_float_to_integer_cast.zig
/home/andy/dev/zig/doc/langref/test_comptime_out_of_bounds_float_to_integer_cast.zig:3:36: error: float value '4294967296' cannot be stored in integer type 'i32'
    const int: i32 = @intFromFloat(float);
                                   ^~~~~

运行时

runtime_out_of_bounds_float_to_integer_cast.zig
pub fn main() void {
    var float: f32 = 4294967296; // runtime-known
    _ = &float;
    const int: i32 = @intFromFloat(float);
    _ = int;
}
Shell
$ zig build-exe runtime_out_of_bounds_float_to_integer_cast.zig
$ ./runtime_out_of_bounds_float_to_integer_cast
thread 200414 panic: integer part of floating point value out of bounds
/home/andy/dev/zig/doc/langref/runtime_out_of_bounds_float_to_integer_cast.zig:4:22: 0x10de8d9 in main (runtime_out_of_bounds_float_to_integer_cast)
    const int: i32 = @intFromFloat(float);
                     ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de272 in posixCallMainAndExit (runtime_out_of_bounds_float_to_integer_cast)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10dde4d in _start (runtime_out_of_bounds_float_to_integer_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

指针转换无效空值 §

当将地址为0的指针转换为不允许地址为0的指针时会发生此情况。例如,C指针可选指针allowzero指针允许地址为零,但普通指针不允许。

编译时

test_comptime_invalid_null_pointer_cast.zig
comptime {
    const opt_ptr: ?*i32 = null;
    const ptr: *i32 = @ptrCast(opt_ptr);
    _ = ptr;
}
Shell
$ zig test test_comptime_invalid_null_pointer_cast.zig
/home/andy/dev/zig/doc/langref/test_comptime_invalid_null_pointer_cast.zig:3:32: error: null pointer casted to type '*i32'
    const ptr: *i32 = @ptrCast(opt_ptr);
                               ^~~~~~~

运行时

runtime_invalid_null_pointer_cast.zig
pub fn main() void {
    var opt_ptr: ?*i32 = null;
    _ = &opt_ptr;
    const ptr: *i32 = @ptrCast(opt_ptr);
    _ = ptr;
}
Shell
$ zig build-exe runtime_invalid_null_pointer_cast.zig
$ ./runtime_invalid_null_pointer_cast
thread 202309 panic: cast causes pointer to be null
/home/andy/dev/zig/doc/langref/runtime_invalid_null_pointer_cast.zig:4:23: 0x10de88c in main (runtime_invalid_null_pointer_cast)
    const ptr: *i32 = @ptrCast(opt_ptr);
                      ^
/home/andy/dev/zig/lib/std/start.zig:651:22: 0x10de252 in posixCallMainAndExit (runtime_invalid_null_pointer_cast)
            root.main();
                     ^
/home/andy/dev/zig/lib/std/start.zig:271:5: 0x10dde2d in _start (runtime_invalid_null_pointer_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

内存 §

Zig语言不为程序员执行内存管理。这就是为什么Zig没有运行时,以及为什么Zig代码可以在如此多的环境中无缝工作的原因,包括实时软件、操作系统内核、嵌入式设备和低延迟服务器。因此,Zig程序员必须始终能够回答以下问题

字节在哪里?

和Zig一样,C编程语言也具有手动内存管理。然而,与Zig不同的是,C语言有一个默认的分配器——mallocreallocfree。当链接libc时,Zig通过std.heap.c_allocator暴露此分配器。然而,按照惯例,Zig中没有默认分配器。相反,需要分配的函数接受一个Allocator参数。同样,像std.ArrayList这样的数据结构在其初始化函数中接受一个Allocator参数

test_allocator.zig
const std = @import("std");
const Allocator = std.mem.Allocator;
const expect = std.testing.expect;

test "using an allocator" {
    var buffer: [100]u8 = undefined;
    var fba = std.heap.FixedBufferAllocator.init(&buffer);
    const allocator = fba.allocator();
    const result = try concat(allocator, "foo", "bar");
    try expect(std.mem.eql(u8, "foobar", result));
}

fn concat(allocator: Allocator, a: []const u8, b: []const u8) ![]u8 {
    const result = try allocator.alloc(u8, a.len + b.len);
    @memcpy(result[0..a.len], a);
    @memcpy(result[a.len..], b);
    return result;
}
Shell
$ zig test test_allocator.zig
1/1 test_allocator.test.using an allocator...OK
All 1 tests passed.

在上面的示例中,100字节的栈内存用于初始化一个FixedBufferAllocator,然后将其传递给一个函数。为方便起见,有一个全局的FixedBufferAllocator可用于快速测试,位于std.testing.allocator,它还将执行基本的内存泄漏检测。

Zig提供了一个通用分配器,可以通过std.heap.GeneralPurposeAllocator导入。然而,仍然建议遵循选择分配器指南。

选择分配器 §

使用哪种分配器取决于多种因素。这里有一个流程图来帮助您决定

  1. 您正在制作一个库吗?在这种情况下,最好接受一个Allocator作为参数,并允许您的库的用户决定使用哪种分配器。
  2. 您正在链接libc吗?在这种情况下,std.heap.c_allocator很可能是正确的选择,至少对于您的主分配器而言。
  3. 需要在多个线程中使用相同的分配器吗?请使用您选择的分配器,并将其包装在std.heap.ThreadSafeAllocator
  4. 您需要的最大字节数是否受限于在comptime已知的一个数字?在这种情况下,请使用std.heap.FixedBufferAllocator
  5. 您的程序是一个命令行应用程序,从头到尾运行,没有任何基本的循环模式(例如视频游戏主循环或Web服务器请求处理程序),因此在结束时一次性释放所有内容是合理的吗?在这种情况下,建议遵循此模式
    cli_allocation.zig
    const std = @import("std");
    
    pub fn main() !void {
        var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
        defer arena.deinit();
    
        const allocator = arena.allocator();
    
        const ptr = try allocator.create(i32);
        std.debug.print("ptr={*}\n", .{ptr});
    }
    Shell
    $ zig build-exe cli_allocation.zig
    $ ./cli_allocation
    ptr=i32@7f86e6953010
    
    使用这种分配器时,无需手动释放任何内容。所有内容都会在调用arena.deinit()时一次性释放。
  6. 分配是循环模式的一部分吗,例如视频游戏主循环或Web服务器请求处理程序?如果所有分配都可以在循环结束时一次性释放,例如一旦视频游戏帧完全渲染完成,或Web服务器请求已处理完毕,那么std.heap.ArenaAllocator是一个很好的选择。如前一个要点所示,这允许您一次性释放整个内存区。另请注意,如果可以确定内存的上限,那么std.heap.FixedBufferAllocator可以用作进一步的优化。
  7. 您正在编写测试,并想确保error.OutOfMemory被正确处理吗?在这种情况下,请使用std.testing.FailingAllocator
  8. 您正在编写测试吗?在这种情况下,请使用std.testing.allocator
  9. 最后,如果以上情况都不适用,您需要一个通用分配器。Zig的通用分配器是一个函数,它接受一个comptime struct配置选项并返回一个类型。通常,您会在主函数中设置一个std.heap.GeneralPurposeAllocator,然后将其或子分配器传递给应用程序的各个部分。
  10. 您还可以考虑实现分配器

字节在哪里? §

字符串字面量,例如"hello"位于全局常量数据段。因此,将字符串字面量传递给可变切片是错误的,如下所示

test_string_literal_to_slice.zig
fn foo(s: []u8) void {
    _ = s;
}

test "string literal to mutable slice" {
    foo("hello");
}
Shell
$ zig test test_string_literal_to_slice.zig
/home/andy/dev/zig/doc/langref/test_string_literal_to_slice.zig:6:9: error: expected type '[]u8', found '*const [5:0]u8'
    foo("hello");
        ^~~~~~~
/home/andy/dev/zig/doc/langref/test_string_literal_to_slice.zig:6:9: note: cast discards const qualifier
/home/andy/dev/zig/doc/langref/test_string_literal_to_slice.zig:1:11: note: parameter type declared here
fn foo(s: []u8) void {
          ^~~~

但是,如果您将切片设为常量,它就可以工作

test_string_literal_to_const_slice.zig
fn foo(s: []const u8) void {
    _ = s;
}

test "string literal to constant slice" {
    foo("hello");
}
Shell
$ zig test test_string_literal_to_const_slice.zig
1/1 test_string_literal_to_const_slice.test.string literal to constant slice...OK
All 1 tests passed.

就像字符串字面量一样,const声明(当值在comptime已知时)存储在全局常量数据段中。同样,编译时变量也存储在全局常量数据段中。

var声明在函数内部存储于函数的栈帧中。一旦函数返回,任何指向函数栈帧中变量的指针都会变成无效引用,并且解引用它们会变成未检查的非法行为

var声明在顶层或结构体声明中存储于全局数据段。

使用allocator.allocallocator.create分配的内存位置由分配器的实现决定。

TODO: 线程局部变量

实现分配器 §

Zig程序员可以通过实现分配器接口来创建自己的分配器。为此,必须仔细阅读std/mem.zig中的文档注释,然后提供一个allocFn和一个resizeFn

有许多示例分配器可供参考。请查看std/heap.zig和std.heap.GeneralPurposeAllocator

堆分配失败 §

许多编程语言选择通过无条件崩溃来处理堆分配失败的可能性。按照惯例,Zig程序员不认为这是一个令人满意的解决方案。相反,error.OutOfMemory表示堆分配失败,每当堆分配失败阻止操作成功完成时,Zig库都会返回此错误代码。

有些人认为,由于某些操作系统(例如Linux)默认启用内存超额承诺(memory overcommit),处理堆分配失败是没有意义的。这种推理存在许多问题

  • 只有部分操作系统具有超额承诺功能。
    • Linux默认启用此功能,但它是可配置的。
    • Windows不进行超额承诺。
    • 嵌入式系统没有超额承诺。
    • 爱好性操作系统可能具有也可能不具有超额承诺。
  • 对于实时系统,不仅没有超额承诺,而且通常每个应用程序的最大内存量都是预先确定的。
  • 编写库时,主要目标之一是代码复用。通过使代码正确处理分配失败,库可以在更多上下文中被复用。
  • 尽管一些软件已经变得依赖于启用超额承诺,但它的存在是无数用户体验灾难的根源。当一个启用了超额承诺的系统(例如默认设置的Linux)接近内存耗尽时,系统会锁死并变得无法使用。此时,OOM Killer会根据启发式算法选择一个应用程序来杀死。这种非确定性决策通常导致重要的进程被杀死,并且常常无法使系统恢复正常工作。

递归 §

递归是软件建模中的一个基本工具。然而,它有一个经常被忽视的问题:无限制的内存分配。

递归是Zig中一个活跃的实验领域,因此这里的文档并非最终版本。您可以阅读0.3.0版本发布说明中关于递归状态的总结

简而言之,目前递归的运作方式符合您的预期。尽管Zig代码尚未受到栈溢出的保护,但计划在未来的Zig版本中提供此类保护,这需要Zig代码一定程度的配合。

生命周期与所有权 §

Zig程序员有责任确保当所指向的内存不再可用时,不访问指针。请注意,切片是一种指针形式,因为它引用了其他内存。

为了防止错误,在处理指针时有一些有用的约定。一般来说,当一个函数返回一个指针时,该函数的文档应该解释谁“拥有”这个指针。这个概念帮助程序员决定何时(如果需要)释放该指针。

例如,函数文档可能会说明“调用者拥有返回的内存”,在这种情况下,调用该函数的代码必须制定何时释放该内存的计划。在这种情况下,函数可能会接受一个Allocator参数。

有时指针的生命周期可能更复杂。例如,std.ArrayList(T).items切片的生命周期在列表下次调整大小(例如通过添加新元素)之前保持有效。

函数和数据结构的API文档应非常仔细地解释指针的所有权和生命周期语义。所有权决定了释放指针引用的内存的责任方,而生命周期决定了内存变得不可访问的时间点(以免发生非法行为)。

编译变量 §

编译变量可以通过导入"builtin"包来访问,编译器将此包提供给每个Zig源文件。它包含编译时常量,例如当前目标、字节序和发布模式。

compile_variables.zig
const builtin = @import("builtin");
const separator = if (builtin.os.tag == .windows) '\\' else '/';

使用@import("builtin")导入的示例

@import("builtin")
const std = @import("std");
/// Zig version. When writing code that supports multiple versions of Zig, prefer
/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks.
pub const zig_version = std.SemanticVersion.parse(zig_version_string) catch unreachable;
pub const zig_version_string = "0.14.1";
pub const zig_backend = std.builtin.CompilerBackend.stage2_llvm;

pub const output_mode: std.builtin.OutputMode = .Exe;
pub const link_mode: std.builtin.LinkMode = .static;
pub const unwind_tables: std.builtin.UnwindTables = .@"async";
pub const is_test = false;
pub const single_threaded = false;
pub const abi: std.Target.Abi = .gnu;
pub const cpu: std.Target.Cpu = .{
    .arch = .x86_64,
    .model = &std.Target.x86.cpu.znver4,
    .features = std.Target.x86.featureSet(&.{
        .@"64bit",
        .adx,
        .aes,
        .allow_light_256_bit,
        .avx,
        .avx2,
        .avx512bf16,
        .avx512bitalg,
        .avx512bw,
        .avx512cd,
        .avx512dq,
        .avx512f,
        .avx512ifma,
        .avx512vbmi,
        .avx512vbmi2,
        .avx512vl,
        .avx512vnni,
        .avx512vpopcntdq,
        .bmi,
        .bmi2,
        .branchfusion,
        .clflushopt,
        .clwb,
        .clzero,
        .cmov,
        .crc32,
        .cx16,
        .cx8,
        .evex512,
        .f16c,
        .fast_15bytenop,
        .fast_bextr,
        .fast_dpwssd,
        .fast_imm16,
        .fast_lzcnt,
        .fast_movbe,
        .fast_scalar_fsqrt,
        .fast_scalar_shift_masks,
        .fast_variable_perlane_shuffle,
        .fast_vector_fsqrt,
        .fma,
        .fsgsbase,
        .fsrm,
        .fxsr,
        .gfni,
        .idivq_to_divl,
        .invpcid,
        .lzcnt,
        .macrofusion,
        .mmx,
        .movbe,
        .mwaitx,
        .nopl,
        .pclmul,
        .pku,
        .popcnt,
        .prfchw,
        .rdpid,
        .rdpru,
        .rdrnd,
        .rdseed,
        .sahf,
        .sbb_dep_breaking,
        .sha,
        .shstk,
        .slow_shld,
        .smap,
        .smep,
        .sse,
        .sse2,
        .sse3,
        .sse4_1,
        .sse4_2,
        .sse4a,
        .ssse3,
        .vaes,
        .vpclmulqdq,
        .vzeroupper,
        .wbnoinvd,
        .x87,
        .xsave,
        .xsavec,
        .xsaveopt,
        .xsaves,
    }),
};
pub const os: std.Target.Os = .{
    .tag = .linux,
    .version_range = .{ .linux = .{
        .range = .{
            .min = .{
                .major = 6,
                .minor = 14,
                .patch = 5,
            },
            .max = .{
                .major = 6,
                .minor = 14,
                .patch = 5,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 39,
            .patch = 0,
        },
        .android = 14,
    }},
};
pub const target: std.Target = .{
    .cpu = cpu,
    .os = os,
    .abi = abi,
    .ofmt = object_format,
    .dynamic_linker = .init("/nix/store/p9kdj55g5l39nbrxpjyz5wc1m0s7rzsx-glibc-2.40-66/lib/ld-linux-x86-64.so.2"),
};
pub const object_format: std.Target.ObjectFormat = .elf;
pub const mode: std.builtin.OptimizeMode = .Debug;
pub const link_libc = false;
pub const link_libcpp = false;
pub const have_error_return_tracing = true;
pub const valgrind_support = true;
pub const sanitize_thread = false;
pub const fuzz = false;
pub const position_independent_code = false;
pub const position_independent_executable = false;
pub const strip_debug_info = false;
pub const code_model: std.builtin.CodeModel = .default;
pub const omit_frame_pointer = false;

另请参阅

编译模型 §

Zig编译被划分为模块。每个模块都是Zig源文件的集合,其中一个文件是模块的根源文件。每个模块都可以依赖任意数量的其他模块,形成一个有向图(模块之间的依赖循环是允许的)。如果模块A依赖于模块B,那么模块A中的任何Zig源文件都可以使用@import并带有模块名称来导入模块B的根源文件。本质上,一个模块充当导入Zig源文件(该文件可能存在于文件系统的完全独立部分)的别名。

一个使用zig build-exe编译的简单Zig程序有两个关键模块:包含您的代码的模块(称为“主”或“根”模块)和标准库。您的模块依赖于名为“std”的标准库模块,这让您可以编写@import("std")!事实上,Zig编译中的每一个模块——包括标准库本身——都隐式地依赖于名为“std”的标准库模块。

“根模块”(您在zig build-exe示例中提供的模块)具有一个特殊属性。与标准库一样,它被隐式地提供给所有模块(包括其自身),这次以“root”为名。因此,@import("root")将始终等同于您的“主”源文件(通常但不一定命名为main.zig)的@import

源文件结构体 §

每个Zig源文件都隐式地是一个struct声明;您可以想象文件的内容被字面上地包围在struct { ... }中。这意味着除了声明之外,文件的顶层也允许包含字段

TopLevelFields.zig
//! Because this file contains fields, it is a type which is intended to be instantiated, and so
//! is named in TitleCase instead of snake_case by convention.

foo: u32,
bar: u64,

/// `@This()` can be used to refer to this struct type. In files with fields, it is quite common to
/// name the type here, so it can be easily referenced by other declarations in this file.
const TopLevelFields = @This();

pub fn init(val: u32) TopLevelFields {
    return .{
        .foo = val,
        .bar = val * 10,
    };
}

此类文件可以像任何其他struct类型一样被实例化。文件的“根结构体类型”可以在该文件内部使用@This引用。

文件与声明发现 §

Zig重视任何代码是否被语义分析的概念;本质上,就是编译器是否“查看”它。分析哪些代码取决于从某个点“发现”了哪些文件和声明。“发现”过程基于一组简单的递归规则

  • 如果对@import的调用被分析,那么被导入的文件也会被分析。
  • 如果一个类型(包括文件)被分析,那么其中所有的comptimeusingnamespaceexport声明也会被分析。
  • 如果一个类型(包括文件)被分析,并且编译是针对测试的,而且该类型所在的模块是编译的根模块,那么其中所有的test声明也会被分析。
  • 如果对命名声明(即其使用)的引用被分析,则被引用的声明也会被分析。声明是与顺序无关的,因此此引用可能位于被引用声明的上方或下方,甚至完全位于另一个文件中。

就是这样!这些规则定义了Zig文件和声明是如何被发现的。剩下的就是理解这个过程从何开始

答案是标准库的根:每个Zig编译都从分析文件lib/std/std.zig开始。此文件包含一个comptime声明,它导入lib/std/start.zig,而该文件又使用@import("root")来引用“根模块”;因此,您作为主模块的根源文件提供的文件实际上也是一个根,因为标准库将始终引用它。

通常希望确保某些声明——特别是testexport声明——被发现。根据上述规则,一种常见的策略是在comptimetest块中使用@import

force_file_discovery.zig
comptime {
    // This will ensure that the file 'api.zig' is always discovered (as long as this file is discovered).
    // It is useful if 'api.zig' contains important exported declarations.
    _ = @import("api.zig");

    // We could also have a file which contains declarations we only want to export depending on a comptime
    // condition. In that case, we can use an `if` statement here:
    if (builtin.os.tag == .windows) {
        _ = @import("windows_api.zig");
    }
}

test {
    // This will ensure that the file 'tests.zig' is always discovered (as long as this file is discovered),
    // if this compilation is a test. It is useful if 'tests.zig' contains tests we want to ensure are run.
    _ = @import("tests.zig");

    // We could also have a file which contains tests we only want to run depending on a comptime condition.
    // In that case, we can use an `if` statement here:
    if (builtin.os.tag == .windows) {
        _ = @import("windows_tests.zig");
    }
}

const builtin = @import("builtin");

特殊根声明 §

由于根模块的根源文件始终可以使用@import("root")访问,因此有时会被库(包括Zig标准库)用作程序向该库暴露一些“全局”信息的位置。Zig标准库将在此文件中查找多个声明。

入口点 §

构建可执行文件时,此文件中要查找的最重要内容是程序的入口点。最常见的情况是,这是一个名为main的函数,它将在执行重要的初始化工作后立即由std.start调用。

或者,名为_start的声明(例如,pub const _start = {};)将禁用默认的std.start逻辑,允许您的根源文件根据需要导出低级入口点。

entry_point.zig
/// `std.start` imports this file using `@import("root")`, and uses this declaration as the program's
/// user-provided entry point. It can return any of the following types:
/// * `void`
/// * `E!void`, for any error set `E`
/// * `u8`
/// * `E!u8`, for any error set `E`
/// Returning a `void` value from this function will exit with code 0.
/// Returning a `u8` value from this function will exit with the given status code.
/// Returning an error value from this function will print an Error Return Trace and exit with code 1.
pub fn main() void {
    std.debug.print("Hello, World!\n", .{});
}

// If uncommented, this declaration would suppress the usual std.start logic, causing
// the `main` declaration above to be ignored.
//pub const _start = {};

const std = @import("std");
Shell
$ zig build-exe entry_point.zig
$ ./entry_point
Hello, World!

如果Zig编译链接libc,main函数可以选择是一个export fn,它匹配C语言main函数的签名

libc_export_entry_point.zig
pub export fn main(argc: c_int, argv: [*]const [*:0]const u8) c_int {
    const args = argv[0..@intCast(argc)];
    std.debug.print("Hello! argv[0] is '{s}'\n", .{args[0]});
    return 0;
}

const std = @import("std");
Shell
$ zig build-exe libc_export_entry_point.zig -lc
$ ./libc_export_entry_point
Hello! argv[0] is './libc_export_entry_point'

std.start在某些情况下也可能使用其他入口点声明,例如wWinMainEfiMain。有关这些声明的详细信息,请参阅lib/std/start.zig逻辑。

标准库选项 §

标准库还在根模块的根源文件中查找名为std_options的声明。如果存在,此声明应为std.Options类型的结构体,并允许程序自定义某些标准库功能,例如std.log的实现。

std_options.zig
/// The presence of this declaration allows the program to override certain behaviors of the standard library.
/// For a full list of available options, see the documentation for `std.Options`.
pub const std_options: std.Options = .{
    // By default, in safe build modes, the standard library will attach a segfault handler to the program to
    // print a helpful stack trace if a segmentation fault occurs. Here, we can disable this, or even enable
    // it in unsafe build modes.
    .enable_segfault_handler = true,
    // This is the logging function used by `std.log`.
    .logFn = myLogFn,
};

fn myLogFn(
    comptime level: std.log.Level,
    comptime scope: @Type(.enum_literal),
    comptime format: []const u8,
    args: anytype,
) void {
    // We could do anything we want here!
    // ...but actually, let's just call the default implementation.
    std.log.defaultLog(level, scope, format, args);
}

const std = @import("std");

Panic处理器 §

Zig标准库在根模块的根源文件中查找名为panic的声明。如果存在,它应该是一个命名空间(容器类型),其中包含提供不同panic处理程序的声明。

有关此命名空间的基本实现,请参见std.debug.simple_panic

覆盖panic处理程序实际输出消息的方式,同时保留默认启用的格式化安全panic,可以通过std.debug.FullPanic轻松实现

panic_handler.zig
pub fn main() void {
    @setRuntimeSafety(true);
    var x: u8 = 255;
    // Let's overflow this integer!
    x += 1;
}

pub const panic = std.debug.FullPanic(myPanic);

fn myPanic(msg: []const u8, first_trace_addr: ?usize) noreturn {
    _ = first_trace_addr;
    std.debug.print("Panic! {s}\n", .{msg});
    std.process.exit(1);
}

const std = @import("std");
Shell
$ zig build-exe panic_handler.zig
$ ./panic_handler
Panic! integer overflow

Zig构建系统 §

Zig构建系统提供了一种跨平台、无依赖的方式来声明构建项目所需的逻辑。通过这个系统,构建项目的逻辑被写入到build.zig文件中,使用Zig构建系统API来声明和配置构建产物及其他任务。

构建系统可以帮助完成的一些任务示例

  • 并行执行任务并缓存结果。
  • 依赖其他项目。
  • 为其他项目提供可依赖的包。
  • 通过执行Zig编译器创建构建产物。这包括构建Zig源代码以及C和C++源代码。
  • 捕获用户配置的选项并使用这些选项配置构建。
  • 通过提供一个可以被Zig代码导入的文件,将构建配置作为comptime值暴露出来。
  • 缓存构建产物,避免不必要的重复步骤。
  • 执行构建产物或系统安装的工具。
  • 运行测试并验证执行构建产物的输出是否与预期值匹配。
  • 在一个代码库或其子集上运行zig fmt
  • 自定义任务。

要使用构建系统,请运行zig build --help以查看命令行使用帮助菜单。这将包括在build.zig脚本中声明的项目特定选项。

目前,构建系统文档托管在外部:构建系统文档

C语言 §

尽管Zig独立于C语言,并且与大多数其他语言不同,不依赖于libc,但Zig承认与现有C代码交互的重要性。

Zig有几种方式促进C语言互操作。

C类型原语 §

这些类型保证与C ABI兼容,并且可以像任何其他类型一样使用。

  • c_char
  • c_short
  • c_ushort
  • c_int
  • c_uint
  • c_long
  • c_ulong
  • c_longlong
  • c_ulonglong
  • c_longdouble

要与C语言的void类型互操作,请使用anyopaque

另请参阅

从C头文件导入 §

@cImport内置函数可用于直接从.h文件导入符号

cImport_builtin.zig
const c = @cImport({
    // See https://github.com/ziglang/zig/issues/515
    @cDefine("_NO_CRT_STDIO_INLINE", "1");
    @cInclude("stdio.h");
});
pub fn main() void {
    _ = c.printf("hello\n");
}
Shell
$ zig build-exe cImport_builtin.zig -lc
$ ./cImport_builtin
hello

@cImport函数接受一个表达式作为参数。该表达式在编译时求值,用于控制预处理指令并包含多个.h文件

@cImport Expression
const builtin = @import("builtin");

const c = @cImport({
    @cDefine("NDEBUG", builtin.mode == .ReleaseFast);
    if (something) {
        @cDefine("_GNU_SOURCE", {});
    }
    @cInclude("stdlib.h");
    if (something) {
        @cUndef("_GNU_SOURCE");
    }
    @cInclude("soundio.h");
});

另请参阅

C语言翻译CLI §

Zig的C语言翻译功能可通过CLI工具zig translate-c获得。它需要一个文件名作为参数,也可以接受一组可选标志,这些标志将转发给clang。它将翻译后的文件写入标准输出。

命令行标志 §

  • -I:指定包含文件的搜索目录。可多次使用。等同于clang的-I标志。当前目录默认包含;使用-I.来包含它。
  • -D:定义预处理器宏。等同于clang的-D标志
  • -cflags [flags] --:将任意附加的命令行标志传递给clang。注意:标志列表必须以--结尾
  • -target:翻译后的Zig代码的目标三元组。如果未指定目标,将使用当前主机目标。

使用-target和-cflags §

重要!当使用zig translate-c翻译C代码时,您必须使用与编译翻译代码时相同的-target三元组。此外,您必须确保所使用的-cflags(如果有)与目标系统上代码使用的cflags匹配。使用不正确的-target-cflags可能导致clang或Zig解析失败,或在与C代码链接时出现微妙的ABI不兼容问题。

varytarget.h
long FOO = __LONG_MAX__;
Shell
$ zig translate-c -target thumb-freestanding-gnueabihf varytarget.h|grep FOO
pub export var FOO: c_long = 2147483647;
$ zig translate-c -target x86_64-macos-gnu varytarget.h|grep FOO
pub export var FOO: c_long = 9223372036854775807;
varycflags.h
enum FOO { BAR };
int do_something(enum FOO foo);
Shell
$ zig translate-c varycflags.h|grep -B1 do_something
pub const enum_FOO = c_uint;
pub extern fn do_something(foo: enum_FOO) c_int;
$ zig translate-c -cflags -fshort-enums -- varycflags.h|grep -B1 do_something
pub const enum_FOO = u8;
pub extern fn do_something(foo: enum_FOO) c_int;

@cImport 与 translate-c 的比较 §

@cImportzig translate-c使用相同的底层C翻译功能,因此在技术层面上它们是等效的。实际上,@cImport作为一种无需额外设置即可快速轻松访问数字常量、typedef和记录类型的方式非常有用。如果您需要将cflags传递给clang,或者您想编辑翻译后的代码,建议使用zig translate-c并将结果保存到文件。编辑生成代码的常见原因包括:将函数式宏中的anytype参数更改为更具体的类型;将[*c]T指针更改为[*]T*T指针以提高类型安全性;以及在特定函数中启用或禁用运行时安全性

另请参阅

C语言翻译缓存 §

C语言翻译功能(无论是通过zig translate-c还是@cImport使用)与Zig缓存系统集成。后续运行中使用相同的源文件、目标和cflags时,将使用缓存,而不是重复翻译相同的代码。

要查看编译使用@cImport的代码时缓存文件存储在哪里,请使用--verbose-cimport标志

verbose_cimport_flag.zig
const c = @cImport({
    @cDefine("_NO_CRT_STDIO_INLINE", "1");
    @cInclude("stdio.h");
});
pub fn main() void {
    _ = c;
}
Shell
$ zig build-exe verbose_cimport_flag.zig -lc --verbose-cimport
info(compilation): C import source: /home/andy/dev/zig/.zig-cache/o/b3cbc430c97e2312555c393b116b6a6e/cimport.h
info(compilation): C import .d file: /home/andy/dev/zig/.zig-cache/o/b3cbc430c97e2312555c393b116b6a6e/cimport.h.d
$ ./verbose_cimport_flag

cimport.h包含要翻译的文件(由调用@cInclude@cDefine@cUndef构造),cimport.h.d是文件依赖列表,cimport.zig包含翻译后的输出。

另请参阅

翻译失败 §

某些C语言构造无法翻译为Zig——例如,goto、带有位字段的结构体和标记拼接宏。Zig采用降级以允许在遇到不可翻译实体时继续翻译。

降级有三种形式——opaqueextern@compileError。无法正确翻译的C结构体和联合体将被翻译为opaque{}。包含不透明类型或无法翻译的代码构造的函数将被降级为extern声明。因此,不可翻译的类型仍然可以用作指针,并且只要链接器知道已编译的函数,就可以调用不可翻译的函数。

@compileError用于当顶层定义(全局变量、函数原型、宏)无法翻译或降级时。由于Zig对顶层声明使用惰性分析,因此不可翻译的实体除非您实际使用它们,否则不会在您的代码中引起编译错误。

另请参阅

C宏 §

C语言翻译会尽力尝试将函数式宏翻译为等效的Zig函数。由于C宏在词法标记级别操作,并非所有C宏都可以翻译为Zig。无法翻译的宏将被降级为@compileError。请注意,使用宏的C代码将毫无问题地被翻译(因为Zig处理的是宏已展开的预处理源代码)。仅仅是宏本身可能无法翻译为Zig。

考虑以下示例

macro.c
#define MAKELOCAL(NAME, INIT) int NAME = INIT
int foo(void) {
   MAKELOCAL(a, 1);
   MAKELOCAL(b, 2);
   return a + b;
}
Shell
$ zig translate-c macro.c > macro.zig
macro.zig
pub export fn foo() c_int {
    var a: c_int = 1;
    _ = &a;
    var b: c_int = 2;
    _ = &b;
    return a + b;
}
pub const MAKELOCAL = @compileError("unable to translate C expr: unexpected token .Equal"); // macro.c:1:9

请注意,尽管使用了不可翻译的宏,但foo仍被正确翻译。MAKELOCAL被降级为@compileError,因为它无法表示为Zig函数;这仅仅意味着您不能直接在Zig中使用MAKELOCAL

另请参阅

C指针 §

这种类型应尽可能避免使用。使用C指针的唯一有效理由是在翻译C代码时自动生成的代码中。

导入C头文件时,指针应该翻译成单项指针(*T)还是多项指针([*]T)是模糊的。C指针是一种折衷方案,以便Zig代码可以直接利用翻译后的头文件。

[*c]T - C指针。

  • 支持其他两种指针类型(*T)和([*]T)的所有语法。
  • 可以强制转换为其他指针类型,以及可选指针。当C指针被强制转换为非可选指针时,如果地址为0,则会发生安全检查的非法行为
  • 允许地址为0。在非独立目标上,解引用地址0是安全检查的非法行为。可选C指针引入了另一个位来跟踪空值,就像?usize一样。请注意,创建可选C指针是不必要的,因为可以使用普通的可选指针
  • 支持类型强制转换与整数之间的转换。
  • 支持与整数的比较。
  • 不支持仅限Zig的指针属性,例如对齐。请使用普通的指针

当C指针指向单个结构体(而非数组)时,解引用该C指针以访问结构体的字段或成员数据。语法如下

ptr_to_struct.*.struct_member

这与C语言中的->操作符类似。

当C指针指向结构体数组时,语法恢复如下

ptr_to_struct_array[index].struct_member

C可变参数函数 §

Zig支持外部可变参数函数。

test_variadic_function.zig
const std = @import("std");
const testing = std.testing;

pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;

test "variadic function" {
    try testing.expect(printf("Hello, world!\n") == 14);
    try testing.expect(@typeInfo(@TypeOf(printf)).@"fn".is_var_args);
}
Shell
$ zig test test_variadic_function.zig -lc
1/1 test_variadic_function.test.variadic function...OK
All 1 tests passed.
Hello, world!

可变参数函数可以使用@cVaStart@cVaEnd@cVaArg@cVaCopy实现。

test_defining_variadic_function.zig
const std = @import("std");
const testing = std.testing;
const builtin = @import("builtin");

fn add(count: c_int, ...) callconv(.C) c_int {
    var ap = @cVaStart();
    defer @cVaEnd(&ap);
    var i: usize = 0;
    var sum: c_int = 0;
    while (i < count) : (i += 1) {
        sum += @cVaArg(&ap, c_int);
    }
    return sum;
}

test "defining a variadic function" {
    if (builtin.cpu.arch == .aarch64 and builtin.os.tag != .macos) {
        // https://github.com/ziglang/zig/issues/14096
        return error.SkipZigTest;
    }
    if (builtin.cpu.arch == .x86_64 and builtin.os.tag == .windows) {
        // https://github.com/ziglang/zig/issues/16961
        return error.SkipZigTest;
    }

    try std.testing.expectEqual(@as(c_int, 0), add(0));
    try std.testing.expectEqual(@as(c_int, 1), add(1, @as(c_int, 1)));
    try std.testing.expectEqual(@as(c_int, 3), add(2, @as(c_int, 1), @as(c_int, 2)));
}
Shell
$ zig test test_defining_variadic_function.zig
1/1 test_defining_variadic_function.test.defining a variadic function...OK
All 1 tests passed.

导出C库 §

Zig的主要用例之一是导出具有C ABI的库,供其他编程语言调用。export关键字放在函数、变量和类型前面,使它们成为库API的一部分

mathtest.zig
export fn add(a: i32, b: i32) i32 {
    return a + b;
}

创建静态库

Shell
$ zig build-lib mathtest.zig

创建共享库

Shell
$ zig build-lib mathtest.zig -dynamic

这是一个使用Zig构建系统的示例

test.c
// This header is generated by zig from mathtest.zig
#include "mathtest.h"
#include <stdio.h>

int main(int argc, char **argv) {
    int32_t result = add(42, 1337);
    printf("%d\n", result);
    return 0;
}
build_c.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const lib = b.addSharedLibrary(.{
        .name = "mathtest",
        .root_source_file = b.path("mathtest.zig"),
        .version = .{ .major = 1, .minor = 0, .patch = 0 },
    });
    const exe = b.addExecutable(.{
        .name = "test",
    });
    exe.addCSourceFile(.{ .file = b.path("test.c"), .flags = &.{"-std=c99"} });
    exe.linkLibrary(lib);
    exe.linkSystemLibrary("c");

    b.default_step.dependOn(&exe.step);

    const run_cmd = exe.run();

    const test_step = b.step("test", "Test the program");
    test_step.dependOn(&run_cmd.step);
}
Shell
$ zig build test
1379

另请参阅

混合目标文件 §

您可以将Zig目标文件与任何遵循C ABI的其他目标文件混合使用。示例

base64.zig
const base64 = @import("std").base64;

export fn decode_base_64(
    dest_ptr: [*]u8,
    dest_len: usize,
    source_ptr: [*]const u8,
    source_len: usize,
) usize {
    const src = source_ptr[0..source_len];
    const dest = dest_ptr[0..dest_len];
    const base64_decoder = base64.standard.Decoder;
    const decoded_size = base64_decoder.calcSizeForSlice(src) catch unreachable;
    base64_decoder.decode(dest[0..decoded_size], src) catch unreachable;
    return decoded_size;
}
test.c
// This header is generated by zig from base64.zig
#include "base64.h"

#include <string.h>
#include <stdio.h>

int main(int argc, char **argv) {
    const char *encoded = "YWxsIHlvdXIgYmFzZSBhcmUgYmVsb25nIHRvIHVz";
    char buf[200];

    size_t len = decode_base_64(buf, 200, encoded, strlen(encoded));
    buf[len] = 0;
    puts(buf);

    return 0;
}
build_object.zig
const std = @import("std");

pub fn build(b: *std.Build) void {
    const obj = b.addObject(.{
        .name = "base64",
        .root_source_file = b.path("base64.zig"),
    });

    const exe = b.addExecutable(.{
        .name = "test",
    });
    exe.addCSourceFile(.{ .file = b.path("test.c"), .flags = &.{"-std=c99"} });
    exe.addObject(obj);
    exe.linkSystemLibrary("c");
    b.installArtifact(exe);
}
Shell
$ zig build
$ ./zig-out/bin/test
all your base are belong to us

另请参阅

WebAssembly §

Zig开箱即用,支持构建WebAssembly。

独立环境 §

对于Web浏览器和nodejs等主机环境,请使用独立操作系统目标构建为可执行文件。这是一个使用nodejs运行编译为WebAssembly的Zig代码的示例。

math.zig
extern fn print(i32) void;

export fn add(a: i32, b: i32) void {
    print(a + b);
}
Shell
$ zig build-exe math.zig -target wasm32-freestanding -fno-entry --export=add
test.js
const fs = require('fs');
const source = fs.readFileSync("./math.wasm");
const typedArray = new Uint8Array(source);

WebAssembly.instantiate(typedArray, {
  env: {
    print: (result) => { console.log(`The result is ${result}`); }
  }}).then(result => {
  const add = result.instance.exports.add;
  add(1, 2);
});
Shell
$ node test.js
The result is 3

WASI §

Zig对WebAssembly系统接口(WASI)的支持正在积极开发中。使用标准库和读取命令行参数的示例

wasi_args.zig
const std = @import("std");

pub fn main() !void {
    var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
    const gpa = general_purpose_allocator.allocator();
    const args = try std.process.argsAlloc(gpa);
    defer std.process.argsFree(gpa, args);

    for (args, 0..) |arg, i| {
        std.debug.print("{}: {s}\n", .{ i, arg });
    }
}
Shell
$ zig build-exe wasi_args.zig -target wasm32-wasi
Shell
$ wasmtime wasi_args.wasm 123 hello
0: wasi_args.wasm
1: 123
2: hello

一个更有趣的例子是从运行时提取预打开(preopens)列表。这现在通过标准库中的std.fs.wasi.Preopens支持。

wasi_preopens.zig
const std = @import("std");
const fs = std.fs;

pub fn main() !void {
    var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
    const gpa = general_purpose_allocator.allocator();

    var arena_instance = std.heap.ArenaAllocator.init(gpa);
    defer arena_instance.deinit();
    const arena = arena_instance.allocator();

    const preopens = try fs.wasi.preopensAlloc(arena);

    for (preopens.names, 0..) |preopen, i| {
        std.debug.print("{}: {s}\n", .{ i, preopen });
    }
}
Shell
$ zig build-exe wasi_preopens.zig -target wasm32-wasi
Shell
$ wasmtime --dir=. wasi_preopens.wasm
0: stdin
1: stdout
2: stderr
3: .

目标 §

目标指的是将用于运行可执行文件的计算机。它由CPU架构、已启用的CPU功能集、操作系统、操作系统最低和最高版本、ABI以及ABI版本组成。

Zig是一种通用编程语言,这意味着它旨在为大量目标生成最优代码。命令zig targets提供了编译器已知的所有目标的信息。

当没有向编译器提供目标选项时,默认选择是针对主机计算机,这意味着生成的可执行文件将不适合复制到不同的计算机。为了将可执行文件复制到另一台计算机,编译器需要通过-target选项了解目标要求。

Zig标准库(@import("std"))具有跨平台抽象,使得相同的源代码在许多目标上都可行。有些代码比其他代码更具可移植性。总的来说,与其它编程语言相比,Zig代码具有极高的可移植性。

每个平台都需要自己的实现才能使Zig的跨平台抽象工作。这些实现处于不同的完成度。编译器的每个标记发布版本都附带发行说明,提供每个目标的完整支持表。

风格指南 §

这些编码约定不受编译器强制执行,但它们随编译器一起发布在本文档中,以便在有人希望引用公认的Zig编码风格权威时提供参考点。

避免名称冗余 §

在类型名称中避免使用以下词语

  • 数据
  • 上下文
  • 管理器
  • utils、misc或某人的首字母缩写

万物皆值,所有类型皆数据,一切皆上下文,所有逻辑都管理状态。使用适用于所有类型的词语,无法传达任何特定信息。

使用“utilities”、“miscellaneous”或某人首字母缩写的诱惑是分类失败的表现,或者更常见的是过度分类。这些声明可以直接放置在需要它们的模块的根部,无需额外的命名空间。

避免在完全限定命名空间中出现冗余名称 §

编译器会为每个声明分配一个完全限定命名空间,从而创建一个树状结构。请根据完全限定命名空间选择名称,并避免冗余的名称段。

redundant_fqn.zig
const std = @import("std");

pub const json = struct {
    pub const JsonValue = union(enum) {
        number: f64,
        boolean: bool,
        // ...
    };
};

pub fn main() void {
    std.debug.print("{s}\n", .{@typeName(json.JsonValue)});
}
Shell
$ zig build-exe redundant_fqn.zig
$ ./redundant_fqn
redundant_fqn.json.JsonValue

在此示例中,“json”在完全限定命名空间中重复。解决方案是从JsonValue中删除Json。在此示例中,我们有一个名为json的空结构体,但请记住文件也作为完全限定命名空间的一部分。

此示例是避免名称冗余中指定规则的例外。该类型的含义已被简化为其核心:它是一个json值。如果不更具体,名称就无法正确。

空白符 §

  • 4空格缩进
  • 左大括号与上一行保持在同一行,除非需要换行。
  • 如果列表项超过2个,将每个项目放在单独的行上,并在末尾添加一个额外的逗号。
  • 行长度:目标100字符;请运用常识。

命名 §

粗略来说:camelCaseFunctionNameTitleCaseTypeNamesnake_case_variable_name。更精确地说

  • 如果x是一个type,则x应使用TitleCase,除非它是一个具有0个字段且不打算实例化的struct,在这种情况下,它被视为“命名空间”并使用snake_case
  • 如果x是可调用的,并且x的返回类型是type,则x应使用TitleCase
  • 如果x是其他可调用的,则x应使用camelCase
  • 否则,x应使用snake_case

首字母缩略词、缩写词、专有名词或任何在英语书写中具有大小写规则的词语,都与其他词语一样受命名约定的约束。即使是只有2个字母长的首字母缩略词也受这些约定的约束。

文件名分为两类:类型和命名空间。如果文件(隐式结构体)具有顶层字段,则应像任何其他具有字段的结构体一样使用TitleCase命名。否则,应使用snake_case。目录名应使用snake_case

这些是通用的经验法则;如果做一些不同的事情更合理,那就做合理的事情。例如,如果存在既定的约定,如ENOENT,则遵循既定约定。

示例 §

style_example.zig
const namespace_name = @import("dir_name/file_name.zig");
const TypeName = @import("dir_name/TypeName.zig");
var global_var: i32 = undefined;
const const_name = 42;
const primitive_type_alias = f32;
const string_alias = []u8;

const StructName = struct {
    field: i32,
};
const StructAlias = StructName;

fn functionName(param_name: TypeName) void {
    var functionPointer = functionName;
    functionPointer();
    functionPointer = otherFunction;
    functionPointer();
}
const functionAlias = functionName;

fn ListTemplateFunction(comptime ChildType: type, comptime fixed_size: usize) type {
    return List(ChildType, fixed_size);
}

fn ShortList(comptime T: type, comptime n: usize) type {
    return struct {
        field_name: [n]T,
        fn methodName() void {}
    };
}

// The word XML loses its casing when used in Zig identifiers.
const xml_document =
    \\<?xml version="1.0" encoding="UTF-8"?>
    \\<document>
    \\</document>
;
const XmlParser = struct {
    field: i32,
};

// The initials BE (Big Endian) are just another word in Zig identifier names.
fn readU32Be() u32 {}

有关更多示例,请参阅Zig标准库

文档注释指南 §

  • 省略任何基于被文档化事物的名称而冗余的信息。
  • 鼓励将信息复制到多个相似函数上,因为这有助于IDE和其他工具提供更好的帮助文本。
  • 使用单词assume来指示当违反时会导致未检查非法行为的不变量。
  • 使用单词assert来指示当违反时会导致安全检查的非法行为的不变量。

源文件编码 §

Zig源代码使用UTF-8编码。无效的UTF-8字节序列会导致编译错误。

在所有Zig源代码中(包括注释中),某些代码点永远不允许使用

  • Ascii控制字符,除了U+000a(LF)、U+000d(CR)和U+0009(HT):U+0000 - U+0008、U+000b - U+000c、U+000e - U+0001f、U+007f。
  • 非Ascii Unicode行尾符:U+0085 (NEL)、U+2028 (LS)、U+2029 (PS)。

LF(字节值0x0a,码点U+000a,'\n')是Zig源代码中的行终止符。这个字节值终止Zig源代码的每一行,除了文件的最后一行。建议非空源文件以空行结束,这意味着最后一个字节将是0x0a(LF)。

每个LF都可以紧接在一个CR(字节值0x0d,码点U+000d,'\r')之后,形成Windows风格的行尾符,但不鼓励这样做。请注意,在多行字符串中,CRLF序列在编译成Zig程序时将被编码为LF。在任何其他上下文中,CR是不允许的。

HT硬制表符(字节值0x09,码点U+0009,'\t')可以与SP空格(字节值0x20,码点U+0020,' ')互换作为标记分隔符,但不鼓励使用硬制表符。请参阅语法

为了与其他工具兼容,如果UTF-8编码的字节顺序标记(U+FEFF)是源代码文本中的第一个Unicode码点,编译器会忽略它。在源代码的其他任何位置都不允许使用字节顺序标记。

请注意,在源文件上运行zig fmt将实现此处提及的所有建议。

请注意,如果假定源代码是正确的Zig代码,读取Zig源代码的工具可以做出假设。例如,在识别行尾时,工具可以使用简单的搜索,例如/\n/,或使用高级搜索,例如/\r\n?|[\n\u0085\u2028\u2029]/,两种情况下行尾都将正确识别。又例如,在识别行中第一个标记之前的空白符时,工具可以使用简单的搜索,例如/[ \t]/,或使用高级搜索,例如/\s/,两种情况下空白符都将正确识别。

关键字参考 §

关键字 描述
addrspace
addrspace关键字。
  • TODO: 添加addrspace的文档
align
align可用于指定指针的对齐方式。它也可以用在变量或函数声明之后,以指定指向该变量或函数的指针的对齐方式。
allowzero
指针属性allowzero允许指针的地址为零。
and
布尔运算符and
anyframe
anyframe可以用作保存指向函数帧的指针的变量的类型。
anytype
函数参数可以使用anytype代替类型声明。类型将在函数调用处推断。
asm
asm开始一个内联汇编表达式。这允许直接控制编译时生成的机器码。
async
async可以在函数调用前使用,以在函数暂停时获取指向函数帧的指针。
await
await可用于暂停当前函数,直到await后面的帧完成。await将目标函数帧返回的值复制到调用者。
break
break可以与块标签一起使用,以从块中返回值。它也可以用于在迭代自然完成之前退出循环。
callconv
callconv可用于在函数类型中指定调用约定。
catch
catch可用于在前面的表达式求值为错误时评估一个表达式。在catch之后的表达式可以选择捕获错误值。
comptime
comptime在一个声明之前使用,可以标记变量或函数参数为编译时已知。它也可以用于保证表达式在编译时运行。
const
const声明一个不能修改的变量。用作指针属性时,它表示指针引用的值不能被修改。
continue
continue可以在循环中使用,跳回循环的开始。
defer
defer将在控制流离开当前块时执行一个表达式。
else
else可用于为ifswitchwhilefor表达式提供一个替代分支。
  • 如果在if表达式之后使用,当测试值为false、null或错误时,else分支将执行。
  • 如果在switch表达式内部使用,当测试值不匹配任何其他情况时,else分支将执行。
  • 如果在循环表达式之后使用,当循环不中断地完成时,else分支将执行。
  • 另请参阅ifswitchwhilefor
enum
enum定义一个枚举类型。
errdefer
errdefer将在控制流离开当前块且函数返回错误时执行一个表达式,该errdefer表达式可以捕获解包后的值。
error
error定义一个错误类型。
export
export使函数或变量在生成的目标文件中外部可见。导出的函数默认使用C调用约定。
extern
extern可用于声明在链接时(静态链接时)或运行时(动态链接时)解析的函数或变量。
fn
fn声明一个函数。
for
for表达式可用于迭代切片、数组或元组的元素。
  • 另请参阅for
if
if表达式可以测试布尔表达式、可选值或错误联合体。对于可选值或错误联合体,if表达式可以捕获解包后的值。
  • 另请参阅if
inline
inline可用于标记循环表达式,使其在编译时展开。它也可以用于强制函数在所有调用点内联。
linksection
linksection关键字可用于指定函数或全局变量将被放置到哪个段(例如.text)。
noalias
noalias关键字。
  • TODO: 添加noalias的文档
noinline
noinline禁止函数在所有调用点内联。
nosuspend
nosuspend关键字可以放在块、语句或表达式前面,以标记一个不会达到暂停点的作用域。特别是,在nosuspend作用域内
  • 使用suspend关键字会导致编译错误。
  • 对尚未完成的函数帧使用await会导致安全检查的非法行为
  • 调用异步函数可能导致安全检查的非法行为,因为它等同于await async some_async_fn(),其中包含一个await
nosuspend作用域内的代码不会导致封闭函数成为异步函数
opaque
opaque定义一个不透明类型。
or
布尔运算符or
orelse
orelse可用于在前面的表达式求值为null时评估一个表达式。
packed
packed关键字放在结构体定义之前,会将结构体的内存布局更改为保证的packed布局。
pub
pub放在顶层声明前面,使该声明可在不同于其声明所在的文件中被引用。
resume
resume将在函数暂停点之后继续执行函数帧。
return
return退出函数并带有一个值。
struct
struct定义一个结构体。
suspend
suspend将导致控制流返回到函数的调用点或恢复器。suspend也可以在函数内部的块之前使用,以允许函数在控制流返回到调用点之前访问其帧。
switch
switch表达式可用于测试常见类型的值。switch的case可以捕获带标签联合体的字段值。
test
test关键字可用于表示用于确保行为符合预期的顶级代码块。
threadlocal
threadlocal可用于将变量指定为线程局部。
try
try评估一个错误联合体表达式。如果它是错误,它将以相同的错误从当前函数返回。否则,表达式结果为解包后的值。
  • 另请参阅try
union
union定义一个联合体。
unreachable
unreachable可用于断言控制流永远不会到达特定位置。根据构建模式,unreachable可能会触发panic。
  • DebugReleaseSafe模式下,或在使用zig test时触发panic。
  • ReleaseFastReleaseSmall模式下不触发panic。
  • 另请参阅unreachable
usingnamespace
usingnamespace是一个顶级声明,它将操作数(必须是结构体、联合体或枚举)的所有公共声明导入到当前作用域。
var
var声明一个可修改的变量。
volatile
volatile可用于表示指针的加载或存储具有副作用。它也可以修改内联汇编表达式以表示其具有副作用。
while
while表达式可用于重复测试布尔、可选或错误联合体表达式,并在该表达式分别求值为false、null或错误时停止循环。

附录 §

容器 §

Zig中的容器是任何充当命名空间以容纳变量函数声明的语法构造。容器也是可以实例化的类型定义。结构体枚举联合体不透明类型,甚至Zig源文件本身都是容器。

尽管容器(Zig源文件除外)使用花括号包围其定义,但它们不应与或函数混淆。容器不包含语句。

语法 §

grammar.y
Root <- skip container_doc_comment? ContainerMembers eof

# *** Top level ***
ContainerMembers <- ContainerDeclaration* (ContainerField COMMA)* (ContainerField / ContainerDeclaration*)

ContainerDeclaration <- TestDecl / ComptimeDecl / doc_comment? KEYWORD_pub? Decl

TestDecl <- KEYWORD_test (STRINGLITERALSINGLE / IDENTIFIER)? Block

ComptimeDecl <- KEYWORD_comptime Block

Decl
    <- (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE? / KEYWORD_inline / KEYWORD_noinline)? FnProto (SEMICOLON / Block)
     / (KEYWORD_export / KEYWORD_extern STRINGLITERALSINGLE?)? KEYWORD_threadlocal? GlobalVarDecl
     / KEYWORD_usingnamespace Expr SEMICOLON

FnProto <- KEYWORD_fn IDENTIFIER? LPAREN ParamDeclList RPAREN ByteAlign? AddrSpace? LinkSection? CallConv? EXCLAMATIONMARK? TypeExpr

VarDeclProto <- (KEYWORD_const / KEYWORD_var) IDENTIFIER (COLON TypeExpr)? ByteAlign? AddrSpace? LinkSection?

GlobalVarDecl <- VarDeclProto (EQUAL Expr)? SEMICOLON

ContainerField <- doc_comment? KEYWORD_comptime? !KEYWORD_fn (IDENTIFIER COLON)? TypeExpr ByteAlign? (EQUAL Expr)?

# *** Block Level ***
Statement
    <- KEYWORD_comptime ComptimeStatement
     / KEYWORD_nosuspend BlockExprStatement
     / KEYWORD_suspend BlockExprStatement
     / KEYWORD_defer BlockExprStatement
     / KEYWORD_errdefer Payload? BlockExprStatement
     / IfStatement
     / LabeledStatement
     / SwitchExpr
     / VarDeclExprStatement

ComptimeStatement
    <- BlockExpr
     / VarDeclExprStatement

IfStatement
    <- IfPrefix BlockExpr ( KEYWORD_else Payload? Statement )?
     / IfPrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )

LabeledStatement <- BlockLabel? (Block / LoopStatement)

LoopStatement <- KEYWORD_inline? (ForStatement / WhileStatement)

ForStatement
    <- ForPrefix BlockExpr ( KEYWORD_else Statement )?
     / ForPrefix AssignExpr ( SEMICOLON / KEYWORD_else Statement )

WhileStatement
    <- WhilePrefix BlockExpr ( KEYWORD_else Payload? Statement )?
     / WhilePrefix AssignExpr ( SEMICOLON / KEYWORD_else Payload? Statement )

BlockExprStatement
    <- BlockExpr
     / AssignExpr SEMICOLON

BlockExpr <- BlockLabel? Block

# An expression, assignment, or any destructure, as a statement.
VarDeclExprStatement
    <- VarDeclProto (COMMA (VarDeclProto / Expr))* EQUAL Expr SEMICOLON
     / Expr (AssignOp Expr / (COMMA (VarDeclProto / Expr))+ EQUAL Expr)? SEMICOLON

# *** Expression Level ***

# An assignment or a destructure whose LHS are all lvalue expressions.
AssignExpr <- Expr (AssignOp Expr / (COMMA Expr)+ EQUAL Expr)?

SingleAssignExpr <- Expr (AssignOp Expr)?

Expr <- BoolOrExpr

BoolOrExpr <- BoolAndExpr (KEYWORD_or BoolAndExpr)*

BoolAndExpr <- CompareExpr (KEYWORD_and CompareExpr)*

CompareExpr <- BitwiseExpr (CompareOp BitwiseExpr)?

BitwiseExpr <- BitShiftExpr (BitwiseOp BitShiftExpr)*

BitShiftExpr <- AdditionExpr (BitShiftOp AdditionExpr)*

AdditionExpr <- MultiplyExpr (AdditionOp MultiplyExpr)*

MultiplyExpr <- PrefixExpr (MultiplyOp PrefixExpr)*

PrefixExpr <- PrefixOp* PrimaryExpr

PrimaryExpr
    <- AsmExpr
     / IfExpr
     / KEYWORD_break BreakLabel? Expr?
     / KEYWORD_comptime Expr
     / KEYWORD_nosuspend Expr
     / KEYWORD_continue BreakLabel?
     / KEYWORD_resume Expr
     / KEYWORD_return Expr?
     / BlockLabel? LoopExpr
     / Block
     / CurlySuffixExpr

IfExpr <- IfPrefix Expr (KEYWORD_else Payload? Expr)?

Block <- LBRACE Statement* RBRACE

LoopExpr <- KEYWORD_inline? (ForExpr / WhileExpr)

ForExpr <- ForPrefix Expr (KEYWORD_else Expr)?

WhileExpr <- WhilePrefix Expr (KEYWORD_else Payload? Expr)?

CurlySuffixExpr <- TypeExpr InitList?

InitList
    <- LBRACE FieldInit (COMMA FieldInit)* COMMA? RBRACE
     / LBRACE Expr (COMMA Expr)* COMMA? RBRACE
     / LBRACE RBRACE

TypeExpr <- PrefixTypeOp* ErrorUnionExpr

ErrorUnionExpr <- SuffixExpr (EXCLAMATIONMARK TypeExpr)?

SuffixExpr
    <- KEYWORD_async PrimaryTypeExpr SuffixOp* FnCallArguments
     / PrimaryTypeExpr (SuffixOp / FnCallArguments)*

PrimaryTypeExpr
    <- BUILTINIDENTIFIER FnCallArguments
     / CHAR_LITERAL
     / ContainerDecl
     / DOT IDENTIFIER
     / DOT InitList
     / ErrorSetDecl
     / FLOAT
     / FnProto
     / GroupedExpr
     / LabeledTypeExpr
     / IDENTIFIER
     / IfTypeExpr
     / INTEGER
     / KEYWORD_comptime TypeExpr
     / KEYWORD_error DOT IDENTIFIER
     / KEYWORD_anyframe
     / KEYWORD_unreachable
     / STRINGLITERAL
     / SwitchExpr

ContainerDecl <- (KEYWORD_extern / KEYWORD_packed)? ContainerDeclAuto

ErrorSetDecl <- KEYWORD_error LBRACE IdentifierList RBRACE

GroupedExpr <- LPAREN Expr RPAREN

IfTypeExpr <- IfPrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?

LabeledTypeExpr
    <- BlockLabel Block
     / BlockLabel? LoopTypeExpr

LoopTypeExpr <- KEYWORD_inline? (ForTypeExpr / WhileTypeExpr)

ForTypeExpr <- ForPrefix TypeExpr (KEYWORD_else TypeExpr)?

WhileTypeExpr <- WhilePrefix TypeExpr (KEYWORD_else Payload? TypeExpr)?

SwitchExpr <- KEYWORD_switch LPAREN Expr RPAREN LBRACE SwitchProngList RBRACE

# *** Assembly ***
AsmExpr <- KEYWORD_asm KEYWORD_volatile? LPAREN Expr AsmOutput? RPAREN

AsmOutput <- COLON AsmOutputList AsmInput?

AsmOutputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN (MINUSRARROW TypeExpr / IDENTIFIER) RPAREN

AsmInput <- COLON AsmInputList AsmClobbers?

AsmInputItem <- LBRACKET IDENTIFIER RBRACKET STRINGLITERAL LPAREN Expr RPAREN

AsmClobbers <- COLON StringList

# *** Helper grammar ***
BreakLabel <- COLON IDENTIFIER

BlockLabel <- IDENTIFIER COLON

FieldInit <- DOT IDENTIFIER EQUAL Expr

WhileContinueExpr <- COLON LPAREN AssignExpr RPAREN

LinkSection <- KEYWORD_linksection LPAREN Expr RPAREN

AddrSpace <- KEYWORD_addrspace LPAREN Expr RPAREN

# Fn specific
CallConv <- KEYWORD_callconv LPAREN Expr RPAREN

ParamDecl
    <- doc_comment? (KEYWORD_noalias / KEYWORD_comptime)? (IDENTIFIER COLON)? ParamType
     / DOT3

ParamType
    <- KEYWORD_anytype
     / TypeExpr

# Control flow prefixes
IfPrefix <- KEYWORD_if LPAREN Expr RPAREN PtrPayload?

WhilePrefix <- KEYWORD_while LPAREN Expr RPAREN PtrPayload? WhileContinueExpr?

ForPrefix <- KEYWORD_for LPAREN ForArgumentsList RPAREN PtrListPayload

# Payloads
Payload <- PIPE IDENTIFIER PIPE

PtrPayload <- PIPE ASTERISK? IDENTIFIER PIPE

PtrIndexPayload <- PIPE ASTERISK? IDENTIFIER (COMMA IDENTIFIER)? PIPE

PtrListPayload <- PIPE ASTERISK? IDENTIFIER (COMMA ASTERISK? IDENTIFIER)* COMMA? PIPE

# Switch specific
SwitchProng <- KEYWORD_inline? SwitchCase EQUALRARROW PtrIndexPayload? SingleAssignExpr

SwitchCase
    <- SwitchItem (COMMA SwitchItem)* COMMA?
     / KEYWORD_else

SwitchItem <- Expr (DOT3 Expr)?

# For specific
ForArgumentsList <- ForItem (COMMA ForItem)* COMMA?

ForItem <- Expr (DOT2 Expr?)?

# Operators
AssignOp
    <- ASTERISKEQUAL
     / ASTERISKPIPEEQUAL
     / SLASHEQUAL
     / PERCENTEQUAL
     / PLUSEQUAL
     / PLUSPIPEEQUAL
     / MINUSEQUAL
     / MINUSPIPEEQUAL
     / LARROW2EQUAL
     / LARROW2PIPEEQUAL
     / RARROW2EQUAL
     / AMPERSANDEQUAL
     / CARETEQUAL
     / PIPEEQUAL
     / ASTERISKPERCENTEQUAL
     / PLUSPERCENTEQUAL
     / MINUSPERCENTEQUAL
     / EQUAL

CompareOp
    <- EQUALEQUAL
     / EXCLAMATIONMARKEQUAL
     / LARROW
     / RARROW
     / LARROWEQUAL
     / RARROWEQUAL

BitwiseOp
    <- AMPERSAND
     / CARET
     / PIPE
     / KEYWORD_orelse
     / KEYWORD_catch Payload?

BitShiftOp
    <- LARROW2
     / RARROW2
     / LARROW2PIPE

AdditionOp
    <- PLUS
     / MINUS
     / PLUS2
     / PLUSPERCENT
     / MINUSPERCENT
     / PLUSPIPE
     / MINUSPIPE

MultiplyOp
    <- PIPE2
     / ASTERISK
     / SLASH
     / PERCENT
     / ASTERISK2
     / ASTERISKPERCENT
     / ASTERISKPIPE

PrefixOp
    <- EXCLAMATIONMARK
     / MINUS
     / TILDE
     / MINUSPERCENT
     / AMPERSAND
     / KEYWORD_try
     / KEYWORD_await

PrefixTypeOp
    <- QUESTIONMARK
     / KEYWORD_anyframe MINUSRARROW
     / SliceTypeStart (ByteAlign / AddrSpace / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
     / PtrTypeStart (AddrSpace / KEYWORD_align LPAREN Expr (COLON Expr COLON Expr)? RPAREN / KEYWORD_const / KEYWORD_volatile / KEYWORD_allowzero)*
     / ArrayTypeStart

SuffixOp
    <- LBRACKET Expr (DOT2 (Expr? (COLON Expr)?)?)? RBRACKET
     / DOT IDENTIFIER
     / DOTASTERISK
     / DOTQUESTIONMARK

FnCallArguments <- LPAREN ExprList RPAREN

# Ptr specific
SliceTypeStart <- LBRACKET (COLON Expr)? RBRACKET

PtrTypeStart
    <- ASTERISK
     / ASTERISK2
     / LBRACKET ASTERISK (LETTERC / COLON Expr)? RBRACKET

ArrayTypeStart <- LBRACKET Expr (COLON Expr)? RBRACKET

# ContainerDecl specific
ContainerDeclAuto <- ContainerDeclType LBRACE container_doc_comment? ContainerMembers RBRACE

ContainerDeclType
    <- KEYWORD_struct (LPAREN Expr RPAREN)?
     / KEYWORD_opaque
     / KEYWORD_enum (LPAREN Expr RPAREN)?
     / KEYWORD_union (LPAREN (KEYWORD_enum (LPAREN Expr RPAREN)? / Expr) RPAREN)?

# Alignment
ByteAlign <- KEYWORD_align LPAREN Expr RPAREN

# Lists
IdentifierList <- (doc_comment? IDENTIFIER COMMA)* (doc_comment? IDENTIFIER)?

SwitchProngList <- (SwitchProng COMMA)* SwitchProng?

AsmOutputList <- (AsmOutputItem COMMA)* AsmOutputItem?

AsmInputList <- (AsmInputItem COMMA)* AsmInputItem?

StringList <- (STRINGLITERAL COMMA)* STRINGLITERAL?

ParamDeclList <- (ParamDecl COMMA)* ParamDecl?

ExprList <- (Expr COMMA)* Expr?

# *** Tokens ***
eof <- !.
bin <- [01]
bin_ <- '_'? bin
oct <- [0-7]
oct_ <- '_'? oct
hex <- [0-9a-fA-F]
hex_ <- '_'? hex
dec <- [0-9]
dec_ <- '_'? dec

bin_int <- bin bin_*
oct_int <- oct oct_*
dec_int <- dec dec_*
hex_int <- hex hex_*

ox80_oxBF <- [\200-\277]
oxF4 <- '\364'
ox80_ox8F <- [\200-\217]
oxF1_oxF3 <- [\361-\363]
oxF0 <- '\360'
ox90_0xBF <- [\220-\277]
oxEE_oxEF <- [\356-\357]
oxED <- '\355'
ox80_ox9F <- [\200-\237]
oxE1_oxEC <- [\341-\354]
oxE0 <- '\340'
oxA0_oxBF <- [\240-\277]
oxC2_oxDF <- [\302-\337]

# From https://lemire.me/blog/2018/05/09/how-quickly-can-you-check-that-a-string-is-valid-unicode-utf-8/
# First Byte      Second Byte     Third Byte      Fourth Byte
# [0x00,0x7F]
# [0xC2,0xDF]     [0x80,0xBF]
#    0xE0         [0xA0,0xBF]     [0x80,0xBF]
# [0xE1,0xEC]     [0x80,0xBF]     [0x80,0xBF]
#    0xED         [0x80,0x9F]     [0x80,0xBF]
# [0xEE,0xEF]     [0x80,0xBF]     [0x80,0xBF]
#    0xF0         [0x90,0xBF]     [0x80,0xBF]     [0x80,0xBF]
# [0xF1,0xF3]     [0x80,0xBF]     [0x80,0xBF]     [0x80,0xBF]
#    0xF4         [0x80,0x8F]     [0x80,0xBF]     [0x80,0xBF]

mb_utf8_literal <-
       oxF4      ox80_ox8F ox80_oxBF ox80_oxBF
     / oxF1_oxF3 ox80_oxBF ox80_oxBF ox80_oxBF
     / oxF0      ox90_0xBF ox80_oxBF ox80_oxBF
     / oxEE_oxEF ox80_oxBF ox80_oxBF
     / oxED      ox80_ox9F ox80_oxBF
     / oxE1_oxEC ox80_oxBF ox80_oxBF
     / oxE0      oxA0_oxBF ox80_oxBF
     / oxC2_oxDF ox80_oxBF

ascii_char_not_nl_slash_squote <- [\000-\011\013-\046\050-\133\135-\177]

char_escape
    <- "\\x" hex hex
     / "\\u{" hex+ "}"
     / "\\" [nr\\t'"]
char_char
    <- mb_utf8_literal
     / char_escape
     / ascii_char_not_nl_slash_squote

string_char
    <- char_escape
     / [^\\"\n]

container_doc_comment <- ('//!' [^\n]* [ \n]* skip)+
doc_comment <- ('///' [^\n]* [ \n]* skip)+
line_comment <- '//' ![!/][^\n]* / '////' [^\n]*
line_string <- ("\\\\" [^\n]* [ \n]*)+
skip <- ([ \n] / line_comment)*

CHAR_LITERAL <- "'" char_char "'" skip
FLOAT
    <- "0x" hex_int "." hex_int ([pP] [-+]? dec_int)? skip
     /      dec_int "." dec_int ([eE] [-+]? dec_int)? skip
     / "0x" hex_int [pP] [-+]? dec_int skip
     /      dec_int [eE] [-+]? dec_int skip
INTEGER
    <- "0b" bin_int skip
     / "0o" oct_int skip
     / "0x" hex_int skip
     /      dec_int   skip
STRINGLITERALSINGLE <- "\"" string_char* "\"" skip
STRINGLITERAL
    <- STRINGLITERALSINGLE
     / (line_string                 skip)+
IDENTIFIER
    <- !keyword [A-Za-z_] [A-Za-z0-9_]* skip
     / "@" STRINGLITERALSINGLE
BUILTINIDENTIFIER <- "@"[A-Za-z_][A-Za-z0-9_]* skip


AMPERSAND            <- '&'      ![=]      skip
AMPERSANDEQUAL       <- '&='               skip
ASTERISK             <- '*'      ![*%=|]   skip
ASTERISK2            <- '**'               skip
ASTERISKEQUAL        <- '*='               skip
ASTERISKPERCENT      <- '*%'     ![=]      skip
ASTERISKPERCENTEQUAL <- '*%='              skip
ASTERISKPIPE         <- '*|'     ![=]      skip
ASTERISKPIPEEQUAL    <- '*|='              skip
CARET                <- '^'      ![=]      skip
CARETEQUAL           <- '^='               skip
COLON                <- ':'                skip
COMMA                <- ','                skip
DOT                  <- '.'      ![*.?]    skip
DOT2                 <- '..'     ![.]      skip
DOT3                 <- '...'              skip
DOTASTERISK          <- '.*'               skip
DOTQUESTIONMARK      <- '.?'               skip
EQUAL                <- '='      ![>=]     skip
EQUALEQUAL           <- '=='               skip
EQUALRARROW          <- '=>'               skip
EXCLAMATIONMARK      <- '!'      ![=]      skip
EXCLAMATIONMARKEQUAL <- '!='               skip
LARROW               <- '<'      ![<=]     skip
LARROW2              <- '<<'     ![=|]     skip
LARROW2EQUAL         <- '<<='              skip
LARROW2PIPE          <- '<<|'    ![=]      skip
LARROW2PIPEEQUAL     <- '<<|='             skip
LARROWEQUAL          <- '<='               skip
LBRACE               <- '{'                skip
LBRACKET             <- '['                skip
LPAREN               <- '('                skip
MINUS                <- '-'      ![%=>|]   skip
MINUSEQUAL           <- '-='               skip
MINUSPERCENT         <- '-%'     ![=]      skip
MINUSPERCENTEQUAL    <- '-%='              skip
MINUSPIPE            <- '-|'     ![=]      skip
MINUSPIPEEQUAL       <- '-|='              skip
MINUSRARROW          <- '->'               skip
PERCENT              <- '%'      ![=]      skip
PERCENTEQUAL         <- '%='               skip
PIPE                 <- '|'      ![|=]     skip
PIPE2                <- '||'               skip
PIPEEQUAL            <- '|='               skip
PLUS                 <- '+'      ![%+=|]   skip
PLUS2                <- '++'               skip
PLUSEQUAL            <- '+='               skip
PLUSPERCENT          <- '+%'     ![=]      skip
PLUSPERCENTEQUAL     <- '+%='              skip
PLUSPIPE             <- '+|'     ![=]      skip
PLUSPIPEEQUAL        <- '+|='              skip
LETTERC              <- 'c'                skip
QUESTIONMARK         <- '?'                skip
RARROW               <- '>'      ![>=]     skip
RARROW2              <- '>>'     ![=]      skip
RARROW2EQUAL         <- '>>='              skip
RARROWEQUAL          <- '>='               skip
RBRACE               <- '}'                skip
RBRACKET             <- ']'                skip
RPAREN               <- ')'                skip
SEMICOLON            <- ';'                skip
SLASH                <- '/'      ![=]      skip
SLASHEQUAL           <- '/='               skip
TILDE                <- '~'                skip

end_of_word <- ![a-zA-Z0-9_] skip
KEYWORD_addrspace   <- 'addrspace'   end_of_word
KEYWORD_align       <- 'align'       end_of_word
KEYWORD_allowzero   <- 'allowzero'   end_of_word
KEYWORD_and         <- 'and'         end_of_word
KEYWORD_anyframe    <- 'anyframe'    end_of_word
KEYWORD_anytype     <- 'anytype'     end_of_word
KEYWORD_asm         <- 'asm'         end_of_word
KEYWORD_async       <- 'async'       end_of_word
KEYWORD_await       <- 'await'       end_of_word
KEYWORD_break       <- 'break'       end_of_word
KEYWORD_callconv    <- 'callconv'    end_of_word
KEYWORD_catch       <- 'catch'       end_of_word
KEYWORD_comptime    <- 'comptime'    end_of_word
KEYWORD_const       <- 'const'       end_of_word
KEYWORD_continue    <- 'continue'    end_of_word
KEYWORD_defer       <- 'defer'       end_of_word
KEYWORD_else        <- 'else'        end_of_word
KEYWORD_enum        <- 'enum'        end_of_word
KEYWORD_errdefer    <- 'errdefer'    end_of_word
KEYWORD_error       <- 'error'       end_of_word
KEYWORD_export      <- 'export'      end_of_word
KEYWORD_extern      <- 'extern'      end_of_word
KEYWORD_fn          <- 'fn'          end_of_word
KEYWORD_for         <- 'for'         end_of_word
KEYWORD_if          <- 'if'          end_of_word
KEYWORD_inline      <- 'inline'      end_of_word
KEYWORD_noalias     <- 'noalias'     end_of_word
KEYWORD_nosuspend   <- 'nosuspend'   end_of_word
KEYWORD_noinline    <- 'noinline'    end_of_word
KEYWORD_opaque      <- 'opaque'      end_of_word
KEYWORD_or          <- 'or'          end_of_word
KEYWORD_orelse      <- 'orelse'      end_of_word
KEYWORD_packed      <- 'packed'      end_of_word
KEYWORD_pub         <- 'pub'         end_of_word
KEYWORD_resume      <- 'resume'      end_of_word
KEYWORD_return      <- 'return'      end_of_word
KEYWORD_linksection <- 'linksection' end_of_word
KEYWORD_struct      <- 'struct'      end_of_word
KEYWORD_suspend     <- 'suspend'     end_of_word
KEYWORD_switch      <- 'switch'      end_of_word
KEYWORD_test        <- 'test'        end_of_word
KEYWORD_threadlocal <- 'threadlocal' end_of_word
KEYWORD_try         <- 'try'         end_of_word
KEYWORD_union       <- 'union'       end_of_word
KEYWORD_unreachable <- 'unreachable' end_of_word
KEYWORD_usingnamespace <- 'usingnamespace' end_of_word
KEYWORD_var         <- 'var'         end_of_word
KEYWORD_volatile    <- 'volatile'    end_of_word
KEYWORD_while       <- 'while'       end_of_word

keyword <- KEYWORD_addrspace / KEYWORD_align / KEYWORD_allowzero / KEYWORD_and
         / KEYWORD_anyframe / KEYWORD_anytype / KEYWORD_asm / KEYWORD_async
         / KEYWORD_await / KEYWORD_break / KEYWORD_callconv / KEYWORD_catch
         / KEYWORD_comptime / KEYWORD_const / KEYWORD_continue / KEYWORD_defer
         / KEYWORD_else / KEYWORD_enum / KEYWORD_errdefer / KEYWORD_error / KEYWORD_export
         / KEYWORD_extern / KEYWORD_fn / KEYWORD_for / KEYWORD_if
         / KEYWORD_inline / KEYWORD_noalias / KEYWORD_nosuspend / KEYWORD_noinline
         / KEYWORD_opaque / KEYWORD_or / KEYWORD_orelse / KEYWORD_packed
         / KEYWORD_pub / KEYWORD_resume / KEYWORD_return / KEYWORD_linksection
         / KEYWORD_struct / KEYWORD_suspend / KEYWORD_switch / KEYWORD_test
         / KEYWORD_threadlocal / KEYWORD_try / KEYWORD_union / KEYWORD_unreachable
         / KEYWORD_usingnamespace / KEYWORD_var / KEYWORD_volatile / KEYWORD_while

编程哲学 §

  • 精确表达意图。
  • 边界情况很重要。
  • 优先阅读代码而非编写代码。
  • 只有一种显而易见的方法来做事情。
  • 运行时崩溃优于错误。
  • 编译错误优于运行时崩溃。
  • 渐进式改进。
  • 避免局部最优。
  • 减少需要记忆的数量。
  • 专注于代码而非风格。
  • 资源分配可能失败;资源释放必须成功。
  • 内存是一种资源。
  • 我们共同服务用户。