← Назад до Вчитися

Огляд

Основні функції

Маленька, проста мова

Зосередьтеся на налагодженні програми, а не на налагодженні своїх знань мови програмування.

Весь синтаксис Zig указано в 580-рядковому файлі граматики PEG.

Немає прихованого потоку керування, прихованого розподілу пам’яті, препроцесора та макросів. Якщо Zig-код не виглядає так, ніби він відскакує, щоб викликати функцію, це не так. Це означає, що ви можете бути впевнені, що наступний код викликає лише foo(), а потім bar(), і це гарантовано без необхідності знати типи чогось:

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

Приклади прихованого потоку керування:

Zig сприяє підтримці та читабельності коду, керуючи всіма потоками керування виключно за допомогою ключових слів мови та викликів функцій.

Продуктивність і Безпека: Виберіть Два

Zig має чотири режими збірки, і всі їх можеа комбінувати і узгоджувати аж до scope гранулярності.

Parameter Debug ReleaseSafe ReleaseFast ReleaseSmall
Оптимізація - покращує швидкість, шкодить налагодженню, шкодить часу компіляції -O3 -O3 -Os
Перевірки безпеки під час виконання – швидкість шкоди, розмір шкоди, збій замість невизначеної поведінки На На

Ось як Переповнення Цілого Числа виглядає під час компіляції, незалежно від режиму збирання:

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

Ось як це виглядає під час виконання, у збірках з перевірками безпеки:

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 3030394 panic: integer overflow
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/2-integer-overflow-runtime.zig:3:7: 0x103cc2e in test.integer overflow at runtime (test)
    x += 1;
      ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:157:25: 0x1047f99 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/compiler/test_runner.zig:37:28: 0x103e01b in main (test)
        return mainTerminal();
                           ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x103d159 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x103ccc1 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/9e3bfcb661c1ce3251fa44aa9886f390/test

Ці стек-трейси працюють на всіх цілях, включаючи окремі.

Використовуючи 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, символи налагодження видалені, однопоточний режим, це створює статичний виконуваний файл розміром 9,8 КБ для цілі x86_64-linux:

$ 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.

Необовʼязковий тип замість null-вказівників

В інших мовах програмування null-вказівники є джерелом багатьох винятків під час виконання, і їх навіть звинувачують як найгіршу помилку інформатики.

Неприкрашені вказівники Zig не можуть мати значення null:

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

Однак будь-який тип можна перетворити на необов’язковий тип, додавши до нього префікс ?:

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.tokenize(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 надає defer і errdefer , щоб зробити керування всіма ресурсами, а не лише пам’яттю, простим і легким для перевірки.

Для прикладу defer див. Інтеграція з бібліотеками C без FFI/bindings. Ось приклад використання 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/assets/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/assets/zig-code/features/12-errors-as-values.zig:4:30: note: consider using 'try', 'catch', or 'if'
referenced by:
    callMain: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:17
    callMainWithArgs: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:12
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

Помилки можна обробляти за допомогою 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

Ключове слово try is a shortcut for catch |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.13.0/lib/std/posix.zig:1768:23: 0x1066a70 in openatZ (14-errors-try)
            .NOENT => return error.FileNotFound,
                      ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1038294 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.13.0/lib/std/fs/Dir.zig:827:5: 0x1034d9e in openFile (14-errors-try)
    return self.openFileZ(&path_c, flags);
    ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/14-errors-try.zig:4:18: 0x1034be8 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 build-exe 15-errors-switch.zig
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:509:45: error: root struct of file '15-errors-switch' has no member named 'main'
    switch (@typeInfo(@typeInfo(@TypeOf(root.main)).Fn.return_type.?)) {
                                        ~~~~^~~~~
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/15-errors-switch.zig:1:1: note: struct declared here
const std = @import("std");
^~~~~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:482:20: note: called from here
    return callMain();
           ~~~~~~~~^~
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:438:36: note: called from here
    std.posix.exit(callMainWithArgs(argc, argv, envp));
                   ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~
referenced by:
    _start: /home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:351:40
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

Ключове слово 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 3030318 panic: attempt to unwrap error: FileNotFound
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/posix.zig:1768:23: 0x10699b0 in openatZ (16-unreachable)
            .NOENT => return error.FileNotFound,
                      ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:880:16: 0x1039a94 in openFileZ (16-unreachable)
    const fd = try posix.openatZ(self.fd, sub_path, os_flags, 0);
               ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/fs/Dir.zig:827:5: 0x103723e in openFile (16-unreachable)
    return self.openFileZ(&path_c, flags);
    ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/16-unreachable.zig:4:77: 0x1034fcf 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.13.0/lib/std/start.zig:514:22: 0x10347d9 in posixCallMainAndExit (16-unreachable)
            root.main();
                     ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:266:5: 0x1034341 in _start (16-unreachable)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Це викликає невизначену поведінку у небезпечних режимах збирання, тому використовуйте його лише тоді, коли успіх гарантований.

Трасування стека на всіх цілях

трасування стека та повернення помилок, показані на цій сторінці, працюють на всіх цілях 1-го рівня і на деяких цілях 2-го рівня підтримки. Навіть окремо!

Крім того, стандартна бібліотека має можливість захопити трасування стека в будь-якій точці, а потім скинути його до стандартної помилки пізніше:

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.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
            addr.* = it.next() orelse {
                            ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:26:32: 0x103717c in foo (17-stack-traces)
    std.debug.captureStackTrace(null, &trace1);
                               ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:16:8: 0x10350c8 in main (17-stack-traces)
    foo();
       ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 in posixCallMainAndExit (17-stack-traces)
            root.main();
                     ^


second one:
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/debug.zig:365:29: 0x10396e7 in captureStackTrace (17-stack-traces)
            addr.* = it.next() orelse {
                            ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:30:32: 0x103719c in bar (17-stack-traces)
    std.debug.captureStackTrace(null, &trace2);
                               ^
/home/ci/actions-runner-website/_work/www.ziglang.org/www.ziglang.org/assets/zig-code/features/17-stack-traces.zig:17:8: 0x10350cd in main (17-stack-traces)
    bar();
       ^
/home/ci/deps/zig-linux-x86_64-0.13.0/lib/std/start.zig:514:22: 0x1034979 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 повністю реалізований у 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.13.0/lib/std/debug.zig:412:14: error: reached unreachable code
assets/zig-code/features/21-comptime.zig:15:15: note: called from here

Інтеграція з бібліотеками C без FFI/bindings

@cImport безпосередньо імпортує типи, змінні, функції та прості макроси для використання в 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 — бібліотеки 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 Build System:

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 може створювати для будь-якої цілі з Таблиці підтримки (see latest release notes) із Підтримкою рівня 3 або кращою. Не потрібно встановлювати "перехресний ланцюжок інструментів" чи щось подібне. Ось рідний 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

Це працює для будь-якої цілі Tier 3+.

Zig постачається з libc

Ви можете знайти доступні цілі libc за допомогою zig targets:

...
 "libc": [
  "aarch64_be-linux-gnu",
  "aarch64_be-linux-musl",
  "aarch64_be-windows-gnu",
  "aarch64-linux-gnu",
  "aarch64-linux-musl",
  "aarch64-windows-gnu",
  "armeb-linux-gnueabi",
  "armeb-linux-gnueabihf",
  "armeb-linux-musleabi",
  "armeb-linux-musleabihf",
  "armeb-windows-gnu",
  "arm-linux-gnueabi",
  "arm-linux-gnueabihf",
  "arm-linux-musleabi",
  "arm-linux-musleabihf",
  "arm-windows-gnu",
  "i386-linux-gnu",
  "i386-linux-musl",
  "i386-windows-gnu",
  "mips64el-linux-gnuabi64",
  "mips64el-linux-gnuabin32",
  "mips64el-linux-musl",
  "mips64-linux-gnuabi64",
  "mips64-linux-gnuabin32",
  "mips64-linux-musl",
  "mipsel-linux-gnu",
  "mipsel-linux-musl",
  "mips-linux-gnu",
  "mips-linux-musl",
  "powerpc64le-linux-gnu",
  "powerpc64le-linux-musl",
  "powerpc64-linux-gnu",
  "powerpc64-linux-musl",
  "powerpc-linux-gnu",
  "powerpc-linux-musl",
  "riscv64-linux-gnu",
  "riscv64-linux-musl",
  "s390x-linux-gnu",
  "s390x-linux-musl",
  "sparc-linux-gnu",
  "sparcv9-linux-gnu",
  "wasm32-freestanding-musl",
  "x86_64-linux-gnu",
  "x86_64-linux-gnux32",
  "x86_64-linux-musl",
  "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 із джерела, а потім зв’язав його. Збірка musl libc для x86_64-linux залишається доступною завдяки системі кешування, тому будь-коли цього 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. Наприклад, загальний розмір заголовків libc, які надсилає Zig, становить 22 МіБ без стиснення. Тим часом заголовки для musl libc + заголовки linux тільки на x86_64 складають 8 МіБ, а для glibc — 3,1 МіБ (у glibc відсутні заголовки linux), але Zig наразі поставляється з 40 бібліотеками libc. З простою комплектацією це було б 444 MiB. Однак завдяки цьому інструменту process_headers і деякій старій добрій ручній праці, двійкові файли архівів Zig залишаються приблизно 30 МіБ, незважаючи на підтримку libc для всіх цих цілей, а також compiler-rt, libunwind і libcxx, і незважаючи на те, що Clang-сумісна компілятор. Для порівняння, сама двійкова збірка Windows clang 8.0.0 від llvm.org становить 132 МіБ.

Зверніть увагу, що лише цілі підтримки рівня 1 були ретельно перевірені. Планується додати більше бібліотек (зокрема для Windows) і [додати тестове покриття для збірки з усіма бібліотеками](https:// github.com/ziglang/zig/issues/2058).

Планується мати Zig Package Manager, але це ще не зроблено. Однією з можливих речей є створення пакета для бібліотек C. Це зробить Zig Build System привабливою як для програмістів Zig, так і для програмістів C.

Система Zig Build

Zig постачається з системою збирання, тому вам не потрібні make, cmake чи щось подібне.

$ zig init-exe
Created build.zig
Created src/main.zig

Next, try `zig build --help` or `zig build run`

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

General Options:
  --help                 Print this help and exit
  --verbose              Print commands before executing them
  --prefix [path]        Override default install prefix
  --search-prefix [path] Add a path to look for binaries, libraries, headers

Project-Specific Options:
  -Dtarget=[string]      The CPU architecture, OS, and ABI to build for.
  -Drelease-safe=[bool]  optimizations on and safety on
  -Drelease-fast=[bool]  optimizations on and safety off
  -Drelease-small=[bool] size optimizations on and safety off

Advanced Options:
  --build-file [file]         Override path to build.zig
  --cache-dir [path]          Override path to zig cache directory
  --override-lib-dir [arg]    Override path to Zig lib directory
  --verbose-tokenize          Enable compiler debug output for tokenization
  --verbose-ast               Enable compiler debug output for parsing into an AST
  --verbose-link              Enable compiler debug output for linking
  --verbose-ir                Enable compiler debug output for Zig IR
  --verbose-llvm-ir           Enable compiler debug output for LLVM IR
  --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 base are belong to us.

Ось кілька прикладів сценаріїв збірки:

Паралелізм через асинхронні функції

Zig 0.5.0 впроваджено асинхронні функції. Ця функція не залежить від операційної системи хоста або навіть від пам’яті, виділеної в купі. Це означає, що асинхронні функції доступні для окремої цілі.

Zig визначає, чи є функція асинхронною, і дозволяє async/await для неасинхронних функцій, що означає, що бібліотеки Zig не залежать від блокування та асинхронного введення-виведення. Zig уникає кольорів функцій.

Стандартна бібліотека Zig реалізує цикл подій, який мультиплексує асинхронні функції в пул потоків для паралельності M:N. Багатопотокова безпека та виявлення перегонів є областями активних досліджень.

Підтримується широкий діапазон цілей

Zig використовує систему «рівня підтримки», щоб повідомляти рівень підтримки для різних цілей.

Таблиця підтримки від Zig 0.11.0

Дружнє ставлення до супроводжуючих пакетів

Еталонний компілятор Zig ще не є повністю самостійним, але незважаючи ні на що, залишиться рівно 3 кроки, щоб перейти від системного компілятора C++ мати повністю самостійний компілятор Zig для будь-якої цілі. Як зазначає Майя Рашіш, перенесення Zig на інші платформи — це весело та швидко.

Неналагоджувальні режими збірки є відтворюваними/детермінованими.

Існує JSON-версія сторінки завантаження.

Кілька членів команди Zig мають досвід обслуговування пакетів.