← 返回学习

概述

功能亮点

小型、简单的语言

专注于调试你的应用程序,而不是调试你的编程语言知识。

Zig 的整个语法都由一个580 行的 PEG 语法文件指定。

没有隐藏的控制流、没有隐藏的内存分配、没有预处理器,也没有宏。如果 Zig 代码看起来不像是在跳转调用一个函数,那么它就不是。这意味着你可以确定以下代码只调用了 foo(),然后是 bar(),并且这不需要知道任何类型就能得到保证。

var a = b + c.d;
foo();
bar();

隐藏控制流的例子

Zig 通过使所有控制流完全由语言关键字和函数调用管理来提高代码的可维护性和可读性。

性能和安全性:二选其二

Zig 有四种构建模式,它们都可以混合搭配,甚至可以细化到作用域粒度

参数调试模式安全发布模式快速发布模式精简发布模式
优化 - 提高速度,损害调试,损害编译时间开启开启开启
运行时安全检查 - 损害速度,损害大小,崩溃而非未定义行为开启开启

下面是整数溢出在编译时(无论构建模式如何)的样子

1-integer-overflow.zig
test "integer overflow at compile time" {
    const x: u8 = 255;
    _ = x + 1;
}
Shell
$ zig test 1-integer-overflow.zig
zig-code/features/1-integer-overflow.zig:3:11: error: overflow of integer type 'u8' with value '256'
    _ = x + 1;
        ~~^~~

下面是它在运行时(在经过安全检查的构建中)的样子

2-integer-overflow-runtime.zig
test "integer overflow at runtime" {
    var x: u8 = 255;
    x += 1;
}
Shell
$ zig test 2-integer-overflow-runtime.zig
1/1 2-integer-overflow-runtime.test.integer overflow at runtime...thread 1954922 panic: integer overflow
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/2-integer-overflow-runtime.zig:3:7: 0x10486b9 in test.integer overflow at runtime (test)
    x += 1;
      ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/compiler/test_runner.zig:214:25: 0x10ee669 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/compiler/test_runner.zig:62:28: 0x10e6bad in main (test)
        return mainTerminal();
                           ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:647:22: 0x10e6132 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:271:5: 0x10e5d0d in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/.zig-cache/o/382e806891f88be376f12feda99b0fac/test --seed=0x7daffd7b

那些堆栈跟踪在所有目标上都有效,包括独立式环境

使用 Zig,可以依赖于启用安全性的构建模式,并在性能瓶颈处选择性地禁用安全性。例如,前面的示例可以这样修改:

3-undefined-behavior.zig
test "actually undefined behavior" {
    @setRuntimeSafety(false);
    var x: u8 = 255;
    x += 1; // XXX undefined behavior!
}

Zig 将未定义行为作为一种锐利的工具,用于防止错误和提升性能。

说到性能,Zig 比 C 语言更快。

请注意,Zig 并非完全安全的语言。对于那些对 Zig 安全性发展感兴趣的人,请关注以下议题:

Zig 与 C 语言竞争,而非依赖它

Zig 标准库与 libc 集成,但并不依赖它。这是 Hello World:

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

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

当使用 -O ReleaseSmall 编译,剥离调试符号,单线程模式时,这会为 x86_64-linux 目标生成一个 9.8 KiB 的静态可执行文件。

$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded
$ wc -c hello
9944 hello
$ ldd hello
  not a dynamic executable

Windows 构建甚至更小,只有 4096 字节。

$ zig build-exe hello.zig -O ReleaseSmall -fstrip -fsingle-threaded -target x86_64-windows
$ wc -c hello.exe
4096 hello.exe
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows

顶层声明的顺序无关性

顶层声明(如全局变量)是顺序无关的,并且是惰性分析的。全局变量的初始值在编译时进行评估

5-global-variables.zig
var y: i32 = add(10, x);
const x: i32 = add(12, 34);

test "global variables" {
    assert(x == 46);
    assert(y == 56);
}

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

const std = @import("std");
const assert = std.debug.assert;
Shell
$ zig test 5-global-variables.zig
1/1 5-global-variables.test.global variables...OK
All 1 tests passed.

可选类型而非空指针

在其他编程语言中,空引用是许多运行时异常的根源,甚至被指责为计算机科学中最糟糕的错误

未修饰的 Zig 指针不能为 null

6-null-to-ptr.zig
test "null @intToPtr" {
    const foo: *i32 = @ptrFromInt(0x0);
    _ = foo;
}
Shell
$ zig test 6-null-to-ptr.zig
zig-code/features/6-null-to-ptr.zig:2:35: error: pointer type '*i32' does not allow address zero
    const foo: *i32 = @ptrFromInt(0x0);
                                  ^~~

然而,任何类型都可以通过在其前面加上 ? 符号来转换为可选类型

7-optional-syntax.zig
const std = @import("std");
const assert = std.debug.assert;

test "null @intToPtr" {
    const ptr: ?*i32 = @ptrFromInt(0x0);
    assert(ptr == null);
}
Shell
$ zig test 7-optional-syntax.zig
1/1 7-optional-syntax.test.null @intToPtr...OK
All 1 tests passed.

要解包一个可选值,可以使用 orelse 来提供一个默认值。

8-optional-orelse.zig
// malloc prototype included for reference
extern fn malloc(size: size_t) ?*u8;

fn doAThing() ?*Foo {
    const ptr = malloc(1234) orelse return null;
    // ...
}

另一种选择是使用 if

9-optional-if.zig
fn doAThing(optional_foo: ?*Foo) void {
    // do some stuff

    if (optional_foo) |foo| {
        doSomethingWithFoo(foo);
    }

    // do some stuff
}

相同的语法也适用于while

10-optional-while.zig
const std = @import("std");

pub fn main() void {
    const msg = "hello this  is dog";
    var it = std.mem.tokenizeAny(u8, msg, " ");
    while (it.next()) |item| {
        std.debug.print("{s}\n", .{item});
    }
}
Shell
$ zig build-exe 10-optional-while.zig
$ ./10-optional-while
hello
this
is
dog

手动内存管理

用 Zig 编写的库可以在任何地方使用

为了实现这一点,Zig 程序员必须自行管理内存,并且必须处理内存分配失败。

Zig 标准库也是如此。任何需要分配内存的函数都接受一个分配器参数。因此,Zig 标准库甚至可以用于独立式目标。

除了一种全新的错误处理方式之外,Zig 还提供了 defererrdefer,使所有资源管理(不仅是内存)变得简单且易于验证。

关于 defer 的示例,请参见无需 FFI/绑定即可与 C 库集成。以下是使用 errdefer 的示例:

11-errdefer.zig
const Device = struct {
    name: []u8,

    fn create(allocator: *Allocator, id: u32) !Device {
        const device = try allocator.create(Device);
        errdefer allocator.destroy(device);

        device.name = try std.fmt.allocPrint(allocator, "Device(id={d})", id);
        errdefer allocator.free(device.name);

        if (id == 0) return error.ReservedDeviceId;

        return device;
    }
};

错误处理新视角

错误是值,不能被忽略

12-errors-as-values.zig
const std = @import("std");

pub fn main() void {
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
}
Shell
$ zig build-exe 12-errors-as-values.zig
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/12-errors-as-values.zig:4:30: error: error union is discarded
    _ = std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
        ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/12-errors-as-values.zig:4:30: note: consider using 'try', 'catch', or 'if'
referenced by:
    posixCallMainAndExit: /home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:647:22
    _start: /home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:464:40
    3 reference(s) hidden; use '-freference-trace=5' to see all references

错误可以通过 catch 处理

13-errors-catch.zig
const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch |err| label: {
        std.debug.print("unable to open file: {}\n", .{err});
        const stderr = std.io.getStdErr();
        break :label stderr;
    };
    file.writeAll("all your codebase are belong to us\n") catch return;
}
Shell
$ zig build-exe 13-errors-catch.zig
$ ./13-errors-catch
unable to open file: error.FileNotFound
all your codebase are belong to us

关键字 trycatch |err| return err 的简写

14-errors-try.zig
const std = @import("std");

pub fn main() !void {
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
    defer file.close();
    try file.writeAll("all your codebase are belong to us\n");
}
Shell
$ zig build-exe 14-errors-try.zig
$ ./14-errors-try
error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/posix.zig:1825:23: 0x10816bd in openatZ (14-errors-try)
            .NOENT => return error.FileNotFound,
                      ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/fs/Dir.zig:886:16: 0x106cb25 in openFileZ (14-errors-try)
    const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
               ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/fs/Dir.zig:833:5: 0x1051ad0 in openFile (14-errors-try)
    return self.openFileZ(&path_c, flags);
    ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/14-errors-try.zig:4:18: 0x10ddf5b in main (14-errors-try)
    const file = try std.fs.cwd().openFile("does_not_exist/foo.txt", .{});
                 ^

请注意,这是一个错误返回跟踪,而不是堆栈跟踪。代码没有为生成该跟踪付出解开堆栈的代价。

对错误使用 switch 关键字可确保处理所有可能的错误

15-errors-switch.zig
const std = @import("std");

test "switch on error" {
    _ = parseInt("hi", 10) catch |err| switch (err) {};
}

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

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

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

        x = try std.math.mul(u64, x, radix);
        x = try std.math.add(u64, x, digit);
    }

    return x;
}

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

    return value;
}
Shell
$ zig test 15-errors-switch.zig
zig-code/features/15-errors-switch.zig:4:40: error: switch must handle all possibilities
    _ = parseInt("hi", 10) catch |err| switch (err) {};
                                       ^~~~~~~~~~~~~~~
zig-code/features/15-errors-switch.zig:4:40: note: unhandled error value: 'error.InvalidCharacter'
zig-code/features/15-errors-switch.zig:4:40: note: unhandled error value: 'error.DigitExceedsRadix'
zig-code/features/15-errors-switch.zig:4:40: note: unhandled error value: 'error.Overflow'

关键字 unreachable 用于断言不会发生错误

16-unreachable.zig
const std = @import("std");

pub fn main() void {
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
    file.writeAll("all your codebase are belong to us\n") catch unreachable;
}
Shell
$ zig build-exe 16-unreachable.zig
$ ./16-unreachable
thread 1955977 panic: attempt to unwrap error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/posix.zig:1825:23: 0x108142d in openatZ (16-unreachable)
            .NOENT => return error.FileNotFound,
                      ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/fs/Dir.zig:886:16: 0x106c895 in openFileZ (16-unreachable)
    const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
               ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/fs/Dir.zig:833:5: 0x1051840 in openFile (16-unreachable)
    return self.openFileZ(&path_c, flags);
    ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/16-unreachable.zig:4:77: 0x10de0ff in main (16-unreachable)
    const file = std.fs.cwd().openFile("does_not_exist/foo.txt", .{}) catch unreachable;
                                                                            ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:647:22: 0x10ddb82 in posixCallMainAndExit (16-unreachable)
            root.main();
                     ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:271:5: 0x10dd75d in _start (16-unreachable)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

这会在不安全的构建模式中触发未定义行为,因此请务必仅在成功有保证的情况下使用它。

所有目标上的堆栈跟踪

此页面上显示的堆栈跟踪和错误返回跟踪适用于所有一级支持目标和部分二级支持目标。甚至在独立式环境中也适用

此外,标准库能够随时捕获堆栈跟踪,并稍后将其转储到标准错误输出。

17-stack-traces.zig
const std = @import("std");

var address_buffer: [8]usize = undefined;

var trace1 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[0..4],
    .index = 0,
};

var trace2 = std.builtin.StackTrace{
    .instruction_addresses = address_buffer[4..],
    .index = 0,
};

pub fn main() void {
    foo();
    bar();

    std.debug.print("first one:\n", .{});
    std.debug.dumpStackTrace(trace1);
    std.debug.print("\n\nsecond one:\n", .{});
    std.debug.dumpStackTrace(trace2);
}

fn foo() void {
    std.debug.captureStackTrace(null, &trace1);
}

fn bar() void {
    std.debug.captureStackTrace(null, &trace2);
}
Shell
$ zig build-exe 17-stack-traces.zig
$ ./17-stack-traces
first one:
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/debug.zig:472:29: 0x10df085 in captureStackTrace (17-stack-traces)
            addr.* = it.next() orelse {
                            ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/17-stack-traces.zig:26:32: 0x10deaac in foo (17-stack-traces)
    std.debug.captureStackTrace(null, &trace1);
                               ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/17-stack-traces.zig:16:8: 0x10de200 in main (17-stack-traces)
    foo();
       ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:647:22: 0x10ddd02 in posixCallMainAndExit (17-stack-traces)
            root.main();
                     ^


second one:
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/debug.zig:472:29: 0x10df085 in captureStackTrace (17-stack-traces)
            addr.* = it.next() orelse {
                            ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/17-stack-traces.zig:30:32: 0x10deacc in bar (17-stack-traces)
    std.debug.captureStackTrace(null, &trace2);
                               ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/zig-code/features/17-stack-traces.zig:17:8: 0x10de209 in main (17-stack-traces)
    bar();
       ^
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/start.zig:647:22: 0x10ddd02 in posixCallMainAndExit (17-stack-traces)
            root.main();
                     ^

你可以在正在进行的GeneralPurposeDebugAllocator 项目中看到此技术的应用。

泛型数据结构和函数

类型是必须在编译时已知的值

18-types.zig
const std = @import("std");
const assert = std.debug.assert;

test "types are values" {
    const T1 = u8;
    const T2 = bool;
    assert(T1 != T2);

    const x: T2 = true;
    assert(x);
}
Shell
$ zig test 18-types.zig
1/1 18-types.test.types are values...OK
All 1 tests passed.

泛型数据结构本质上是一个返回 type 的函数

19-generics.zig
const std = @import("std");

fn List(comptime T: type) type {
    return struct {
        items: []T,
        len: usize,
    };
}

pub fn main() void {
    var buffer: [10]i32 = undefined;
    var list = List(i32){
        .items = &buffer,
        .len = 0,
    };
    list.items[0] = 1234;
    list.len += 1;

    std.debug.print("{d}\n", .{list.items.len});
}
Shell
$ zig build-exe 19-generics.zig
$ ./19-generics
10

编译时反射和编译时代码执行

内置函数 @typeInfo 提供反射功能

20-reflection.zig
const std = @import("std");

const Header = struct {
    magic: u32,
    name: []const u8,
};

pub fn main() void {
    printInfoAboutStruct(Header);
}

fn printInfoAboutStruct(comptime T: type) void {
    const info = @typeInfo(T);
    inline for (info.@"struct".fields) |field| {
        std.debug.print(
            "{s} has a field called {s} with type {s}\n",
            .{
                @typeName(T),
                field.name,
                @typeName(field.type),
            },
        );
    }
}
Shell
$ zig build-exe 20-reflection.zig
$ ./20-reflection
20-reflection.Header has a field called magic with type u32
20-reflection.Header has a field called name with type []const u8

Zig 标准库使用此技术来实现格式化打印。尽管 Zig 是一个小型、简单的语言,但其格式化打印完全用 Zig 实现。与此同时,在 C 语言中,printf 的编译错误是硬编码到编译器中的。类似地,在 Rust 中,格式化打印宏也是硬编码到编译器中的。

Zig 还能在编译时评估函数和代码块。在某些上下文中,例如全局变量初始化,表达式会在编译时隐式评估。否则,可以使用 comptime 关键字显式地在编译时评估代码。当与断言结合使用时,这会特别强大。

21-comptime.zig
const std = @import("std");
const assert = std.debug.assert;

fn fibonacci(x: u32) u32 {
    if (x <= 1) return x;
    return fibonacci(x - 1) + fibonacci(x - 2);
}

test "compile-time evaluation" {
    var array: [fibonacci(6)]i32 = undefined;

    @memset(&array, 42);

    comptime {
        assert(array.len == 12345);
    }
}
Shell
$ zig test 21-comptime.zig
/home/ci/deps/zig-linux-x86_64-0.14.0/lib/std/debug.zig:522:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^~~~~~~~~~~
zig-code/features/21-comptime.zig:15:15: note: called from here
        assert(array.len == 12345);
        ~~~~~~^~~~~~~~~~~~~~~~~~~~

无需 FFI/绑定即可与 C 库集成

@cImport 直接导入 C 语言的类型、变量、函数和简单宏,供 Zig 使用。它甚至可以将 C 语言的内联函数翻译成 Zig。

这是一个使用 libsoundio 发出正弦波的示例:

sine.zig

22-sine-wave.zig
const c = @cImport(@cInclude("soundio/soundio.h"));
const std = @import("std");

fn sio_err(err: c_int) !void {
    switch (err) {
        c.SoundIoErrorNone => {},
        c.SoundIoErrorNoMem => return error.NoMem,
        c.SoundIoErrorInitAudioBackend => return error.InitAudioBackend,
        c.SoundIoErrorSystemResources => return error.SystemResources,
        c.SoundIoErrorOpeningDevice => return error.OpeningDevice,
        c.SoundIoErrorNoSuchDevice => return error.NoSuchDevice,
        c.SoundIoErrorInvalid => return error.Invalid,
        c.SoundIoErrorBackendUnavailable => return error.BackendUnavailable,
        c.SoundIoErrorStreaming => return error.Streaming,
        c.SoundIoErrorIncompatibleDevice => return error.IncompatibleDevice,
        c.SoundIoErrorNoSuchClient => return error.NoSuchClient,
        c.SoundIoErrorIncompatibleBackend => return error.IncompatibleBackend,
        c.SoundIoErrorBackendDisconnected => return error.BackendDisconnected,
        c.SoundIoErrorInterrupted => return error.Interrupted,
        c.SoundIoErrorUnderflow => return error.Underflow,
        c.SoundIoErrorEncodingString => return error.EncodingString,
        else => return error.Unknown,
    }
}

var seconds_offset: f32 = 0;

fn write_callback(
    maybe_outstream: ?[*]c.SoundIoOutStream,
    frame_count_min: c_int,
    frame_count_max: c_int,
) callconv(.C) void {
    _ = frame_count_min;
    const outstream: *c.SoundIoOutStream = &maybe_outstream.?[0];
    const layout = &outstream.layout;
    const float_sample_rate: f32 = @floatFromInt(outstream.sample_rate);
    const seconds_per_frame = 1.0 / float_sample_rate;
    var frames_left = frame_count_max;

    while (frames_left > 0) {
        var frame_count = frames_left;

        var areas: [*]c.SoundIoChannelArea = undefined;
        sio_err(c.soundio_outstream_begin_write(
            maybe_outstream,
            @ptrCast(&areas),
            &frame_count,
        )) catch |err| std.debug.panic("write failed: {s}", .{@errorName(err)});

        if (frame_count == 0) break;

        const pitch = 440.0;
        const radians_per_second = pitch * 2.0 * std.math.pi;
        var frame: c_int = 0;
        while (frame < frame_count) : (frame += 1) {
            const float_frame: f32 = @floatFromInt(frame);
            const sample = std.math.sin((seconds_offset + float_frame *
                seconds_per_frame) * radians_per_second);
            {
                var channel: usize = 0;
                while (channel < @as(usize, @intCast(layout.channel_count))) : (channel += 1) {
                    const channel_ptr = areas[channel].ptr;
                    const sample_ptr: *f32 = @alignCast(@ptrCast(&channel_ptr[@intCast(areas[channel].step * frame)]));
                    sample_ptr.* = sample;
                }
            }
        }
        const float_frame_count: f32 = @floatFromInt(frame_count);
        seconds_offset += seconds_per_frame * float_frame_count;

        sio_err(c.soundio_outstream_end_write(maybe_outstream)) catch |err| std.debug.panic("end write failed: {s}", .{@errorName(err)});

        frames_left -= frame_count;
    }
}

pub fn main() !void {
    const soundio = c.soundio_create();
    defer c.soundio_destroy(soundio);

    try sio_err(c.soundio_connect(soundio));

    c.soundio_flush_events(soundio);

    const default_output_index = c.soundio_default_output_device_index(soundio);
    if (default_output_index < 0) return error.NoOutputDeviceFound;

    const device = c.soundio_get_output_device(soundio, default_output_index) orelse return error.OutOfMemory;
    defer c.soundio_device_unref(device);

    std.debug.print("Output device: {s}\n", .{device.*.name});

    const outstream = c.soundio_outstream_create(device) orelse return error.OutOfMemory;
    defer c.soundio_outstream_destroy(outstream);

    outstream.*.format = c.SoundIoFormatFloat32NE;
    outstream.*.write_callback = write_callback;

    try sio_err(c.soundio_outstream_open(outstream));

    try sio_err(c.soundio_outstream_start(outstream));

    while (true) c.soundio_wait_events(soundio);
}

$ zig build-exe sine.zig -lsoundio -lc
$ ./sine
Output device: Built-in Audio Analog Stereo
^C

这段 Zig 代码比等效的 C 代码简单得多,并且具有更多的安全保护,所有这些都是通过直接导入 C 头文件实现的,无需 API 绑定。

Zig 在使用 C 库方面比 C 语言更出色。

Zig 也是一个 C 编译器

这是一个 Zig 构建 C 代码的示例:

hello.c

#include <stdio.h>

int main(int argc, char **argv) {
    printf("Hello world\n");
    return 0;
}
$ zig build-exe hello.c --library c
$ ./hello
Hello world

你可以使用 --verbose-cc 来查看它执行了哪个 C 编译器命令。

$ zig build-exe hello.c --library c --verbose-cc
zig cc -MD -MV -MF .zig-cache/tmp/42zL6fBH8fSo-hello.o.d -nostdinc -fno-spell-checking -isystem /home/andy/dev/zig/build/lib/zig/include -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-gnu -isystem /home/andy/dev/zig/build/lib/zig/libc/include/generic-glibc -isystem /home/andy/dev/zig/build/lib/zig/libc/include/x86_64-linux-any -isystem /home/andy/dev/zig/build/lib/zig/libc/include/any-linux-any -march=native -g -fstack-protector-strong --param ssp-buffer-size=4 -fno-omit-frame-pointer -o .zig-cache/tmp/42zL6fBH8fSo-hello.o -c hello.c -fPIC

请注意,如果你再次运行该命令,将不会有输出,并且它会立即完成。

$ time zig build-exe hello.c --library c --verbose-cc

real	0m0.027s
user	0m0.018s
sys	0m0.009s

这得益于构建产物缓存。Zig 使用强大的缓存系统自动解析 .d 文件,以避免重复工作。

Zig 不仅能编译 C 代码,而且使用 Zig 作为 C 编译器有一个非常好的理由:Zig 自带 libc

导出函数、变量和类型供 C 代码依赖

Zig 的主要用例之一是导出具有 C ABI 的库,供其他编程语言调用。函数、变量和类型前面的 export 关键字使它们成为库 API 的一部分。

mathtest.zig

23-math-test.zig
export fn add(a: i32, b: i32) i32 {
    return a + b;
}

创建静态库

$ zig build-lib mathtest.zig

创建共享库

$ zig build-lib mathtest.zig -dynamic

这是一个使用Zig 构建系统的示例:

test.c

#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.zig

24-build.zig
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0));

    const exe = b.addExecutable("test", null);
    exe.addCSourceFile("test.c", &[_][]const u8{"-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);
}

$ zig build test
1379

交叉编译是一流用例

Zig 可以为支持表(请参阅最新发布说明)中任何三级或更高级别支持的目标进行构建。无需安装“交叉工具链”之类的东西。这是一个原生的 Hello World:

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

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

现在为 x86_64-windows、x86_64-macos 和 aarch64-linux 构建它。

$ zig build-exe hello.zig -target x86_64-windows
$ file hello.exe
hello.exe: PE32+ executable (console) x86-64, for MS Windows
$ zig build-exe hello.zig -target x86_64-macos
$ file hello
hello: Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|PIE>
$ zig build-exe hello.zig -target aarch64-linux
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, with debug_info, not stripped

这适用于任何三级及以上目标,可以为任何三级及以上目标构建。

Zig 自带 libc

你可以通过 zig targets 找到可用的 libc 目标。

...
.libc = .{
  "arc-linux-gnu",
  "arm-freebsd-eabihf",
  "arm-linux-gnueabi",
  "arm-linux-gnueabihf",
  "arm-linux-musleabi",
  "arm-linux-musleabihf",
  "arm-netbsd-eabi",
  "arm-netbsd-eabihf",
  "armeb-linux-gnueabi",
  "armeb-linux-gnueabihf",
  "armeb-linux-musleabi",
  "armeb-linux-musleabihf",
  "armeb-netbsd-eabi",
  "armeb-netbsd-eabihf",
  "thumb-linux-musleabi",
  "thumb-linux-musleabihf",
  "thumb-windows-gnu",
  "thumbeb-linux-musleabi",
  "thumbeb-linux-musleabihf",
  "aarch64-freebsd-none",
  "aarch64-linux-gnu",
  "aarch64-linux-musl",
  "aarch64-macos-none",
  "aarch64-netbsd-none",
  "aarch64-windows-gnu",
  "aarch64_be-linux-gnu",
  "aarch64_be-linux-musl",
  "aarch64_be-netbsd-none",
  "csky-linux-gnueabi",
  "csky-linux-gnueabihf",
  "hexagon-linux-musl",
  "loongarch64-linux-gnu",
  "loongarch64-linux-gnusf",
  "loongarch64-linux-musl",
  "loongarch64-linux-muslsf",
  "m68k-linux-gnu",
  "m68k-linux-musl",
  "m68k-netbsd-none",
  "mips-linux-gnueabi",
  "mips-linux-gnueabihf",
  "mips-linux-musleabi",
  "mips-linux-musleabihf",
  "mips-netbsd-eabi",
  "mips-netbsd-eabihf",
  "mipsel-linux-gnueabi",
  "mipsel-linux-gnueabihf",
  "mipsel-linux-musleabi",
  "mipsel-linux-musleabihf",
  "mipsel-netbsd-eabi",
  "mipsel-netbsd-eabihf",
  "mips64-linux-gnuabi64",
  "mips64-linux-gnuabin32",
  "mips64-linux-muslabi64",
  "mips64-linux-muslabin32",
  "mips64el-linux-gnuabi64",
  "mips64el-linux-gnuabin32",
  "mips64el-linux-muslabi64",
  "mips64el-linux-muslabin32",
  "powerpc-freebsd-eabihf",
  "powerpc-linux-gnueabi",
  "powerpc-linux-gnueabihf",
  "powerpc-linux-musleabi",
  "powerpc-linux-musleabihf",
  "powerpc-netbsd-eabi",
  "powerpc-netbsd-eabihf",
  "powerpc64-freebsd-none",
  "powerpc64-linux-gnu",
  "powerpc64-linux-musl",
  "powerpc64le-freebsd-none",
  "powerpc64le-linux-gnu",
  "powerpc64le-linux-musl",
  "riscv32-linux-gnu",
  "riscv32-linux-musl",
  "riscv64-freebsd-none",
  "riscv64-linux-gnu",
  "riscv64-linux-musl",
  "s390x-linux-gnu",
  "s390x-linux-musl",
  "sparc-linux-gnu",
  "sparc-netbsd-none",
  "sparc64-linux-gnu",
  "sparc64-netbsd-none",
  "wasm32-wasi-musl",
  "x86-freebsd-none",
  "x86-linux-gnu",
  "x86-linux-musl",
  "x86-netbsd-none",
  "x86-windows-gnu",
  "x86_64-freebsd-none",
  "x86_64-linux-gnu",
  "x86_64-linux-gnux32",
  "x86_64-linux-musl",
  "x86_64-linux-muslx32",
  "x86_64-macos-none",
  "x86_64-netbsd-none",
  "x86_64-windows-gnu",
},
...

这意味着这些目标使用的 --library c 不依赖于任何系统文件

让我们再次看看那个C 语言 Hello World 示例

$ zig build-exe hello.c --library c
$ ./hello
Hello world
$ ldd ./hello
	linux-vdso.so.1 (0x00007ffd03dc9000)
	libc.so.6 => /lib/libc.so.6 (0x00007fc4b62be000)
	libm.so.6 => /lib/libm.so.6 (0x00007fc4b5f29000)
	libpthread.so.0 => /lib/libpthread.so.0 (0x00007fc4b5d0a000)
	libdl.so.2 => /lib/libdl.so.2 (0x00007fc4b5b06000)
	librt.so.1 => /lib/librt.so.1 (0x00007fc4b58fe000)
	/lib/ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fc4b6672000)

glibc 不支持静态构建,但 musl 支持。

$ zig build-exe hello.c --library c -target x86_64-linux-musl
$ ./hello
Hello world
$ ldd hello
  not a dynamic executable

在这个示例中,Zig 从源代码构建了 musl libc,然后与之链接。得益于缓存系统,为 x86_64-linux 构建的 musl libc 仍然可用,因此任何时候再次需要此 libc 时,它都将立即可用。

这意味着此功能可在任何平台上使用。Windows 和 macOS 用户可以构建 Zig 和 C 代码,并链接到 libc,以支持上述任何目标。同样,代码也可以交叉编译到其他架构。

$ zig build-exe hello.c --library c -target aarch64-linux-gnu
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 2.0.0, with debug_info, not stripped

在某些方面,Zig 比 C 编译器更像一个 C 编译器!

此功能不仅仅是将交叉编译工具链与 Zig 捆绑在一起。例如,Zig 附带的 libc 头文件总大小(未压缩)为 22 MiB。同时,仅 x86_64 上的 musl libc + linux 头文件就达到了 8 MiB,而 glibc 则为 3.1 MiB(glibc 缺少 linux 头文件),然而 Zig 目前附带了 40 个 libc。如果简单地捆绑所有这些,将达到 444 MiB。然而,多亏了这款 process_headers 工具,以及一些传统的人工工作,尽管支持所有这些目标的 libc、compiler-rt、libunwind 和 libcxx,并且作为 clang 兼容的 C 编译器,Zig 的二进制 tar 包总大小仍保持在约 30 MiB。相比之下,llvm.org 上 clang 8.0.0 本身在 Windows 上的二进制构建为 132 MiB。

请注意,目前只有一级支持目标经过了全面测试。计划添加更多 libc(包括 Windows),并增加对所有 libc 的构建测试覆盖

计划推出一个 Zig 包管理器,但目前尚未完成。其中一项可能实现的功能是为 C 库创建包。这将使Zig 构建系统对 Zig 程序员和 C 程序员都具有吸引力。

Zig 构建系统

Zig 自带构建系统,因此你不需要 make、cmake 或类似工具。

$ zig init
info: created build.zig
info: created build.zig.zon
info: created src/main.zig
info: created src/root.zig
info: see `zig build --help` for a menu of options

src/main.zig

25-all-bases.zig
const std = @import("std");

pub fn main() anyerror!void {
    std.debug.print("All your base are belong to us.\n");
}

build.zig

26-build.zig
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
    const mode = b.standardReleaseOptions();
    const exe = b.addExecutable("example", "src/main.zig");
    exe.setBuildMode(mode);

    const run_cmd = exe.run();

    const run_step = b.step("run", "Run the app");
    run_step.dependOn(&run_cmd.step);

    b.default_step.dependOn(&exe.step);
    b.installArtifact(exe);
}

让我们看看 --help 菜单。

$ zig build --help
Usage: zig build [steps] [options]

Steps:
  install (default)            Copy build artifacts to prefix path
  uninstall                    Remove build artifacts from prefix path
  run                          Run the app
  test                         Run unit tests

General Options:
  -p, --prefix [path]          Where to install files (default: zig-out)
  --prefix-lib-dir [path]      Where to install libraries
  --prefix-exe-dir [path]      Where to install executables
  --prefix-include-dir [path]  Where to install C header files

  --release[=mode]             Request release mode, optionally specifying a
                               preferred optimization mode: fast, safe, small

  -fdarling,  -fno-darling     Integration with system-installed Darling to
                               execute macOS programs on Linux hosts
                               (default: no)
  -fqemu,     -fno-qemu        Integration with system-installed QEMU to execute
                               foreign-architecture programs on Linux hosts
                               (default: no)
  --glibc-runtimes [path]      Enhances QEMU integration by providing glibc built
                               for multiple foreign architectures, allowing
                               execution of non-native programs that link with glibc.
  -frosetta,  -fno-rosetta     Rely on Rosetta to execute x86_64 programs on
                               ARM64 macOS hosts. (default: no)
  -fwasmtime, -fno-wasmtime    Integration with system-installed wasmtime to
                               execute WASI binaries. (default: no)
  -fwine,     -fno-wine        Integration with system-installed Wine to execute
                               Windows programs on Linux hosts. (default: no)

  -h, --help                   Print this help and exit
  -l, --list-steps             Print available steps
  --verbose                    Print commands before executing them
  --color [auto|off|on]        Enable or disable colored error messages
  --prominent-compile-errors   Buffer compile errors and display at end
  --summary [mode]             Control the printing of the build summary
    all                        Print the build summary in its entirety
    new                        Omit cached steps
    failures                   (Default) Only print failed steps
    none                       Do not print the build summary
  -j<N>                        Limit concurrent jobs (default is to use all CPU cores)
  --maxrss <bytes>             Limit memory usage (default is to use available memory)
  --skip-oom-steps             Instead of failing, skip steps that would exceed --maxrss
  --fetch                      Exit after fetching dependency tree
  --watch                      Continuously rebuild when source files are modified
  --fuzz                       Continuously search for unit test failures
  --debounce <ms>              Delay before rebuilding after changed file detected
     -fincremental             Enable incremental compilation
  -fno-incremental             Disable incremental compilation

Project-Specific Options:
  -Dtarget=[string]            The CPU architecture, OS, and ABI to build for
  -Dcpu=[string]               Target CPU features to add or subtract
  -Dofmt=[string]              Target object format
  -Ddynamic-linker=[string]    Path to interpreter on the target system
  -Doptimize=[enum]            Prioritize performance, safety, or binary size
                                 Supported Values:
                                   Debug
                                   ReleaseSafe
                                   ReleaseFast
                                   ReleaseSmall

System Integration Options:
  --search-prefix [path]       Add a path to look for binaries, libraries, headers
  --sysroot [path]             Set the system root directory (usually /)
  --libc [file]                Provide a file which specifies libc paths

  --system [pkgdir]            Disable package fetching; enable all integrations
  -fsys=[name]                 Enable a system integration
  -fno-sys=[name]              Disable a system integration

  Available System Integrations:                Enabled:
  (none)                                        -

Advanced Options:
  -freference-trace[=num]      How many lines of reference trace should be shown per compile error
  -fno-reference-trace         Disable reference trace
  -fallow-so-scripts           Allows .so files to be GNU ld scripts
  -fno-allow-so-scripts        (default) .so files must be ELF files
  --build-file [file]          Override path to build.zig
  --cache-dir [path]           Override path to local Zig cache directory
  --global-cache-dir [path]    Override path to global Zig cache directory
  --zig-lib-dir [arg]          Override path to Zig lib directory
  --build-runner [file]        Override path to build runner
  --seed [integer]             For shuffling dependency traversal order (default: random)
  --debug-log [scope]          Enable debugging the compiler
  --debug-pkg-config           Fail if unknown pkg-config flags encountered
  --debug-rt                   Debug compiler runtime libraries
  --verbose-link               Enable compiler debug output for linking
  --verbose-air                Enable compiler debug output for Zig AIR
  --verbose-llvm-ir[=file]     Enable compiler debug output for LLVM IR
  --verbose-llvm-bc=[file]     Enable compiler debug output for LLVM BC
  --verbose-cimport            Enable compiler debug output for C imports
  --verbose-cc                 Enable compiler debug output for C compilation
  --verbose-llvm-cpu-features  Enable compiler debug output for LLVM CPU features

你可以看到其中一个可用的步骤是 run。

$ zig build run
All your codebase are belong to us.
Run `zig build test` to run the tests.

以下是一些构建脚本示例:

支持广泛的目标

Zig 使用“支持层级”系统来表明对不同目标的支援程度。

截至 Zig 0.14.0 的支持表

对包维护者友好

参考 Zig 编译器尚未完全自举,但无论如何,从拥有一个系统 C++ 编译器到拥有一个适用于任何目标的完全自举的 Zig 编译器,都将保持精确的 3 个步骤。正如 Maya Rashish 指出的,将 Zig 移植到其他平台既有趣又快速

非调试构建模式是可复现/确定性的。

有一个下载页面的 JSON 版本

Zig 团队的几位成员拥有维护软件包的经验。