Spec-Zone.ru › Zig

Справочник языка Zig

Введение

Zig — это универсальный язык программирования и инструментальная среда для разработки надёжного, эффективного и повторно используемого программного обеспечения.

Надёжный
Поведение корректно даже в крайних случаях, таких как недостаток памяти.
Эффективный
Напишите программы наилучшим способом для их поведения и производительности.
Повторно используемый
Один и тот же код работает во многих средах с разными ограничениями.
Поддерживаемый
Точно передайте намерения компилятору и другим программистам. Язык налагает небольшой накладной при чтении кода и устойчив к изменениям требований и среды.

Часто наиболее эффективным способом изучить что-то новое является изучение примеров, поэтому данная документация демонстрирует использование каждого из функций Zig. Всё расположено на одной странице, чтобы вы могли использовать инструмент поиска вашего браузера.

Примеры кода в этом документе компилируются и тестируются как часть основного набора тестов Zig.

Этот HTML-документ не зависит от внешних файлов, поэтому вы можете использовать его автономно.

Стандартная библиотека Zig

Стандартная библиотека Zig имеет свою документацию.

Стандартная библиотека Zig содержит часто используемые алгоритмы, структуры данных и определения, которые помогут вам создавать программы или библиотеки. Вы увидите много примеров использования Стандартной библиотеки Zig в этой документации. Чтобы узнать больше о Стандартной библиотеке Zig, посетите ссылку выше.

Привет, мир

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

pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    try stdout.print("Hello, {s}!\n", .{"world"});
}
Командная строка
$ 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", .{});
}
Командная строка
$ zig build-exe hello_again.zig
$ ./hello_again
Hello, world!

В этом случае ! может быть опущено из типа возвращаемого значения, поскольку функция не возвращает ошибок.

См. также:

  • Значения
  • @import
  • Ошибки
  • Основной исходный файл
  • Кодировка исходного кода

Комментарии

Zig поддерживает 3 типа комментариев. Обычные комментарии игнорируются, но комментарии к документации и комментарии к документации верхнего уровня используются компилятором для генерации документации пакета.

Сгенерированная документация всё ещё находится в стадии разработки и может быть сгенерирована с помощью:

Командная строка
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
}
Командная строка
$ 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");
Командная строка
$ zig build-obj invalid_doc-comment.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
Командная строка
$ zig build-obj unattached_doc-comment.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,
    });
}
Командная строка
$ 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 для совместимости ABI с C
c_short short для совместимости ABI с C
c_ushort unsigned short для совместимости ABI с C
c_int int для совместимости ABI с C
c_uint unsigned int для совместимости ABI с C
c_long long для совместимости ABI с C
c_ulong unsigned long для совместимости ABI с C
c_longlong long long для совместимости ABI с C
c_ulonglong unsigned long long для совместимости ABI с C
c_longdouble long double для совместимости ABI с C
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 double 80-битное число с плавающей точкой (64-битный мантисса) IEEE-754-2008 80-битное расширенное точность
f128 _Float128 128-битное число с плавающей точкой (112-битный мантисса) IEEE-754-2008 binary128
bool bool true или false
anyopaque void Используется для типов указателей без типов.
void (нет) Всегда значение void{}
noreturn (нет) Тип break, continue, return, unreachable, и while (true) {}
type (нет) тип типов
anyerror (нет) код ошибки
comptime_int (нет) Допускается только для известных во время компиляции значений. Тип целочисленных литералов.
comptime_float (нет) Допускается только для известных во время компиляции значений. Тип чисел с плавающей точкой.

В дополнение к вышеперечисленным целочисленным типам, целочисленные типы произвольной разрядности можно ссылаться, используя идентификатор i или u, за которым следуют цифры. Например, идентификатор i7 относится к целому числу со знаком 7 бит. Максимальная разрешенная разрядность целочисленного типа — 65535.

См. также:

  • Целые числа
  • Числа с плавающей точкой
  • void
  • Ошибки
  • @Type

Примитивные значения

Примитивные значения
Имя Описание
true и false bool значения
null используется для установки необязательного типа null
undefined используется для оставления значения неопределенным

См. также:

  • Дополнительные
  • undefined

Литералы строк и литералы кодовых точек Юникода

Литералы строк — это постоянные одноэлементные указатели на нуль-терминированные массивы байтов. Тип литералов строк кодирует как длину, так и тот факт, что они нуль-терминированы, и поэтому их можно привести как к фрагментам, так и к указателям с нуль-терминатором. Разъяснение литералов строк преобразует их в массивы.

Поскольку исходный код Zig закодирован в UTF-8, любые байты, отличные от ASCII, которые появляются в строковом литерале в исходном коде, сохраняют своё значение UTF-8 в содержимом строки в программе Zig; байты не изменяются компилятором. Можно встроить байты, не являющиеся UTF-8, в строковый литерал, используя обозначение \xNN.

Индексация в строку, содержащую байты, отличные от ASCII, возвращает отдельные байты, независимо от того, являются ли они допустимым UTF-8 или нет.

Литералы кодовых точек Юникода имеют тип comptime_int, такой же, как литералы целых чисел. Все последовательности обратного слэша допустимы как в строковых литералах, так и в литералах кодовых точек Юникода.

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
}
Командная строка
$ 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} Шестнадцатеричное кодовое значение Юникода, закодированное в UTF-8 (1 или более цифр)

Обратите внимание, что максимальное допустимое значение кодовой точки Юникода — 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;
    \\}
;

См. также:

  • @embedFile

Присваивание

Используйте ключевое слово 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();
}
Командная строка
$ zig build-exe constant_identifier_cannot_change.zig
/home/andy/src/zig/doc/langref/constant_identifier_cannot_change.zig:8:7: error: cannot assign to constant
    y += 1;
    ~~^~~~
referenced by:
    main: /home/andy/src/zig/doc/langref/constant_identifier_cannot_change.zig:12:5
    callMain: /home/andy/src/zig/lib/std/start.zig:514:17
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

const относится ко всем байтам, которые идентификатор непосредственно адресует. Указатели имеют свою константность.

Если вам нужна переменная, которую можно изменить, используйте ключевое слово var:

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

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

    y += 1;

    print("{d}", .{y});
}
Командная строка
$ zig build-exe mutable_var.zig
$ ./mutable_var
5679

Переменные должны быть инициализированы:

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

    x = 1;
}
Командная строка
$ zig build-exe var_must_be_initialized.zig
/home/andy/src/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});
}
Командная строка
$ zig build-exe assign_undefined.zig
$ ./assign_undefined
1

undefined можно привести к любому типу. После этого невозможно определить, что значение является undefined. undefined означает, что значение может быть любым, даже не имеющим смысла в соответствии с типом. Переводя на английский, undefined означает "Не имеющее смысла значение. Использование этого значения является ошибкой. Значение не будет использовано или будет перезаписано до использования."

В режиме отладки Zig записывает 0xaa байт в неопределённую память. Это делается для раннего обнаружения ошибок и для помощи в обнаружении использования неопределённой памяти в отладчике. Однако это поведение является только особенностью реализации, а не семантикой языка, поэтому оно не гарантировано будет наблюдаемо кодом.

Тестирование 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;
}
Командная строка
$ 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 для обеспечения того, что она возвращает 42 при входном значении 41. С точки зрения этого теста, функция addOne называется тестируемым кодом.

zig test — это инструмент, который создаёт и запускает тестовую сборку. По умолчанию он создаёт и запускает исполняемый файл, используя стандартный тестовый запускер, предоставленный стандартной библиотекой Zig, как основную точку входа. Во время сборки test объявления, найденные при разрешении данного файла Zig, включаются для запуска и отчёта стандартного тестового запускаера.

Данная документация описывает особенности стандартного тестового запускаера, предоставляемого стандартной библиотекой Zig. Его исходный код находится по адресу lib/test_runner.zig.

Вывод командной строки, показанный выше, отображает две строки после команды zig test. Эти строки выводятся в стандартный вывод ошибок стандартным тестовым запускаером:

1/2 testing_introduction.test.expect addOne adds one to 41...
Строки такого вида указывают, какой тест из общего числа тестов выполняется. В данном случае 1/2 означает, что выполняется первый тест из двух. Обратите внимание, что при выводе стандартного вывода ошибок запускаера тестов в терминал, эти строки очищаются при успешном прохождении теста.
2/2 testing_introduction.decltest.addOne...
Когда имя теста — идентификатор, стандартный тестовый запускаер использует текст decltest вместо test.
All 2 tests passed.
Эта строка указывает общее количество пройденных тестов.

Объявления тестов

Объявления тестов содержат ключевое слово test, за которым следует необязательное имя, записанное как строковый литерал или идентификатор, за которым следует блок, содержащий любой допустимый код Zig, разрешённый в функции.

Блоки тестов без имени всегда выполняются во время тестовых сборок и освобождаются от пропуска тестов.

Объявления тестов похожи на функции: они имеют тип возвращаемого значения и блок кода. Неявный тип возвращаемого значения test — это союз типа ошибки anyerror!void, и его нельзя изменить. Когда файл Zig не компилируется с помощью инструмента zig test, объявления тестов исключаются из сборки.

Объявления тестов могут быть записаны в том же файле, где написан тестируемый код, или в отдельном файле Zig. Поскольку объявления тестов являются объявлениями верхнего уровня, они не зависят от порядка и могут быть записаны до или после тестируемого кода.

См. также:

  • Глобальный набор ошибок
  • Грамматика

Примеры тестов в документации

Объявления тестов, имеющие имя, использующее идентификатор, являются примерами тестов в документации. Идентификатор должен ссылаться на другое объявление в области видимости. Пример теста в документации, как и комментарий к документации, служит документацией к связанному объявлению и будет отображаться в сгенерированной документации для объявления.

Эффективный пример теста в документации должен быть самодостаточным и сосредоточенным на тестируемом объявлении, отвечая на вопросы нового пользователя о его интерфейсе или предполагаемом использовании, избегая ненужных или запутанных деталей. Пример теста в документации не заменяет комментарий к документации, а дополняет и сопровождает его, предоставляя проверяемый, основанный на коде пример, проверенный с помощью 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);
}
Командная строка
$ zig test testing_failure.zig
1/2 testing_failure.test.expect this to fail...FAIL (TestUnexpectedResult)
/home/andy/src/zig/lib/std/testing.zig:540:14: 0x103ce3f in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/home/andy/src/zig/doc/langref/testing_failure.zig:4:5: 0x103cf55 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/src/zig/.zig-cache/o/054f0b6f088824f384d1b6c648523593/test

Пропуск тестов

Один из способов пропуска тестов — отфильтровать их с помощью параметра командной строки zig test --test-filter [текст]. Это заставляет тестовую сборку включать только тесты, имена которых содержат указанный текстовый фильтр. Обратите внимание, что тесты без имени запускаются даже при использовании параметра командной строки --test-filter [текст].

Для программирования пропуска теста, сделайте test возвращает ошибку error.SkipZigTest, и стандартный тестовый запускаер будет рассматривать тест как пропущенный. Общее количество пропущенных тестов будет сообщено после выполнения всех тестов.

testing_skip.zig
test "this will be skipped" {
    return error.SkipZigTest;
}
Командная строка
$ 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);
}
Оболочка
$ zig test testing_detect_leak.zig
1/1 testing_detect_leak.test.detect leak...OK
[gpa] (err): memory address 0x7fdbea69e000 leaked:
/home/andy/src/zig/lib/std/array_list.zig:457:67: 0x104f76e in ensureTotalCapacityPrecise (test)
                const new_memory = try self.allocator.alignedAlloc(T, alignment, new_capacity);
                                                                  ^
/home/andy/src/zig/lib/std/array_list.zig:434:51: 0x1045610 in ensureTotalCapacity (test)
            return self.ensureTotalCapacityPrecise(better_capacity);
                                                  ^
/home/andy/src/zig/lib/std/array_list.zig:483:41: 0x1041fe0 in addOne (test)
            try self.ensureTotalCapacity(newlen);
                                        ^
/home/andy/src/zig/lib/std/array_list.zig:262:49: 0x103ef2d in append (test)
            const new_item_ptr = try self.addOne();
                                                ^
/home/andy/src/zig/doc/langref/testing_detect_leak.zig:6:20: 0x103d172 in test.detect leak (test)
    try list.append('☔');
                   ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104c6a0 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x10428bb in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103f429 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103ef91 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/src/zig/.zig-cache/o/4a17198138bf81bcfabd5652b0d6be24/test

См. также:

  • defer
  • Память

Обнаружение тестовой сборки

Используйте переменную компиляции @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;
}
Оболочка
$ zig test testing_detect_test.zig
1/1 testing_detect_test.test.builtin.is_test...OK
All 1 tests passed.

Вывод и регистрация тестов

По умолчанию, тестовый запуск и пространство имён тестирования Zig Standard Library выводят сообщения в стандартный поток ошибок.

Пространство имён тестирования

Пространство имён testing Zig Standard Library содержит полезные функции для создания тестов. Помимо функции 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);
}
Оболочка
$ 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 Standard Library также содержит функции для сравнения фрагментов, строк и многое другое. См. остальную часть пространства имён std.testing в Zig Standard Library для получения дополнительных доступных функций.

Документация по инструменту тестирования

zig test имеет несколько параметров командной строки, которые влияют на компиляцию. См. zig test --help для получения полного списка.

Переменные

Переменная — это единица хранения памяти.

В целом, предпочтительно использовать const вместо var при объявлении переменной. Это снижает нагрузку на людей и компьютеры при чтении кода и создаёт больше возможностей для оптимизации.

Ключевое слово extern или встроенная функция @extern могут использоваться для связи с переменной, экспортированной из другого объекта. Ключевое слово export или встроенная функция @export могут использоваться для обеспечения доступа к переменной другим объектам во время линковки. В обоих случаях тип переменной должен быть совместим с C ABI.

См. также:

  • Экспорт библиотеки C

Идентификаторы

Идентификаторы переменных никогда не могут затенять идентификаторы из внешней области видимости.

Идентификаторы должны начинаться с буквенного символа или подчёркивания и могут быть продолжены любым количеством буквенно-цифровых символов или подчёркиваний. Они не должны совпадать с ключевыми словами. См. Справочник по ключевым словам.

Если необходимо имя, которое не соответствует этим требованиям, например, для связи с внешними библиотеками, может использоваться синтаксис @"".

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";

Переменные уровня контейнера

Переменные уровня контейнера имеют статический жизненный цикл, не зависят от порядка и анализируются лениво. Значение инициализации переменных уровня контейнера неявно comptime.

Если переменная уровня контейнера const, то её значение известно comptime, в противном случае — известно во время выполнения.

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;
Оболочка
$ zig test test_container_level_variables.zig
1/1 test_container_level_variables.test.container level variables...OK
All 1 tests passed.

Переменные уровня контейнера могут быть объявлены внутри структуры, объединения, перечисления или непрозрачного типа:

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;
}
Оболочка
$ 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;
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_thread_local_variables.zig
1/1 test_thread_local_variables.test.thread local storage...OK
All 1 tests passed.

Для однопоточных сборок все переменные, локальные для потока, обрабатываются как обычные переменные уровня контейнера.

Переменные, локальные для потока, не могут быть const.

Локальные переменные

Локальные переменные встречаются внутри функций, comptime блоков и @cImport блоков.

Если локальная переменная const, это означает, что после инициализации значение переменной не будет меняться. Если начальное значение const переменной известно на стадии comptime, то переменная также является comptime-известной.

Локальная переменная может быть квалифицирована ключевым словом comptime. Это приводит к тому, что значение переменной будет известно на стадии comptime, а все загрузки и сохранения переменной будут происходить во время семантического анализа программы, а не во время выполнения. Все переменные, объявленные в выражении 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");
    }
}
Оболочка
$ 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;
}

В этой функции значения a и b известны только во время выполнения и, следовательно, это операция деления уязвима как для переполнения целых чисел, так и для деления на ноль.

Операторы, такие как + и -, вызывают неопределённое поведение при переполнении целых чисел. Для обёртывания и насыщения арифметики на всех целевых платформах предусмотрены альтернативные операторы. +% и -% выполняют обёртывающую арифметику, а +| и -| — насыщающую.

Zig поддерживает целые числа с произвольной разрядностью, к которым обращаются, используя идентификатор i или u и цифры. Например, идентификатор i7 относится к знаковое целое число 7 бит. Максимальная разрешённая разрядность целочисленного типа составляет 65535. Для знаковых целочисленных типов Zig использует дополнение до двух представление.

См. также:

  • Операции обёртывания

Вещественные числа

Zig имеет следующие типы с плавающей точкой:

  • f16 - IEEE-754-2008 двоичная16
  • f32 - IEEE-754-2008 двоичная32
  • f64 - IEEE-754-2008 двоичная64
  • f80 - IEEE-754-2008 расширенная 80-битная точность
  • f128 - IEEE-754-2008 двоичная128
  • c_longdouble - соответствует long double для целевого C ABI

Вещественные литералы

Вещественные литералы имеют тип 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;
}
Оболочка
$ 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)});
}

См. также:

  • @setFloatMode
  • Деление на ноль

Операторы

Перегрузки операторов нет. Когда вы видите оператор в Zig, вы знаете, что он выполняет действие из этой таблицы и ничего более.

Таблица операторов

Название Синтаксис Типы Примечания Пример
END_OF_DOCUMENT_MARKER
Сложение
a + b
a += b
  • Целые числа
  • Вещественные числа
  • Может вызвать переполнение для целых чисел.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @addWithOverflow.
2 + 5 == 7
Обёртывающее сложение
a +% b
a +%= b
  • Целые числа
  • Поведение обёртывания в дополнительном коде.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @addWithOverflow.
@as(u32, 0xffffffff) +% 1 == 0
Насыщающее сложение
a +| b
a +|= b
  • Целые числа
  • Вызывает разрешение типа операндов для операндов.
@as(u8, 255) +| 1 == @as(u8, 255)
Вычитание
a - b
a -= b
  • Целые числа
  • Вещественные числа
  • Может вызвать переполнение для целых чисел.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @subWithOverflow.
2 - 5 == -3
Обёртывающее вычитание
a -% b
a -%= b
  • Целые числа
  • Поведение обёртывания в дополнительном коде.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @subWithOverflow.
@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
  • Целые числа
  • Вещественные числа
  • Может вызвать переполнение для целых чисел.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @mulWithOverflow.
2 * 5 == 10
Обёртывающее умножение
a *% b
a *%= b
  • Целые числа
  • Поведение обёртывания в дополнительном коде.
  • Вызывает разрешение типа операндов для операндов.
  • См. также @mulWithOverflow.
@as(u8, 200) *% 2 == 144
Насыщающее умножение
a *| b
a *|= b
  • Целые числа
  • Вызывает разрешение типа операндов для операндов.
@as(u8, 200) *| 2 == 255
Деление
a / b
a /= b
  • Целые числа
  • Вещественные числа
  • Может вызвать переполнение для целых чисел.
  • Может вызвать деление на ноль для целых чисел.
  • Может вызвать деление на ноль для вещественных чисел в режиме FloatMode.Optimized.
  • Операнды со знаком должны быть известны во время компиляции и положительными. В других случаях используйте @divTrunc, @divFloor или @divExact вместо этого.
  • Вызывает разрешение типа операндов для операндов.
10 / 5 == 2
Остаток от деления
a % b
a %= b
  • Целые числа
  • Вещественные числа
  • Может вызвать деление на ноль для целых чисел.
  • Может вызвать деление на ноль для вещественных чисел в режиме FloatMode.Optimized.
  • Операнды со знаком или вещественные операнды должны быть известны во время компиляции и положительными. В других случаях используйте @rem или @mod вместо этого.
  • Вызывает разрешение типа операндов для операндов.
10 % 3 == 1
Побитовый сдвиг влево
a << b
a <<= b
  • Целые числа
  • Перемещает все биты влево, вставляя новые нули в младший бит.
  • b должен быть известен во время компиляции или иметь тип с числом двоичных разрядов, как a.
  • См. также @shlExact.
  • См. также @shlWithOverflow.
0b1 << 8 == 0b100000000
Насыщающий побитовый сдвиг влево
a <<| b
a <<|= b
  • Целые числа
  • См. также @shlExact.
  • См. также @shlWithOverflow.
@as(u8, 1) <<| 8 == 255
Побитовый сдвиг вправо
a >> b
a >>= b
  • Целые числа
  • Перемещает все биты вправо, вставляя нули в старший бит.
  • b должен быть известен во время компиляции или иметь тип с числом двоичных разрядов, как a.
  • См. также @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
  • Необязательные значения
Если a равно null, возвращает 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 может быть значением типа noreturn. err — это error и находится в области выражения b.
const value: anyerror!u32 = error.Broken;
const unwrapped = value catch 1234;
unwrapped == 1234
Логическое И
a and b
  • bool
Если a равно false, возвращает false без вычисления b. В противном случае возвращает b.
(false and true) == false
Логическое ИЛИ
a or b
  • bool
Если a равно true, возвращает true без вычисления b. В противном случае возвращает b.
(false or true) == true
Логическое НЕ
!a
  • bool
!false == true
Равенство
a == b
  • Целые числа
  • Вещественные числа
  • bool
  • type
Возвращает true если a и b равны, иначе возвращает false. Вызывает разрешение типа операндов для операндов.
(1 == 1) == true
Проверка на null
a == null
  • Необязательные значения
Возвращает true если a равно null, иначе возвращает false.
const value: ?u32 = null;
(value == null) == true
Неравенство
a != b
  • Целые числа
  • Вещественные числа
  • bool
  • type
Возвращает false если a и b равны, иначе возвращает true. Вызывает разрешение типа операндов для операндов.
(1 != 1) == false
Проверка на не-null
a != null
  • Необязательные значения
Возвращает false если a равно null, иначе возвращает true.
const value: ?u32 = null;
(value != null) == false
END_OF_DOCUMENT_MARKER
Больше, чем
a > b
  • Целые_числа
  • Числа с плавающей точкой
Возвращает true если a больше b, в противном случае возвращает false. Вызывает Разрешение_типов_операндов для операндов.
(2 > 1) == true
Больше или равно
a >= b
  • Целые_числа
  • Числа с плавающей точкой
Возвращает true если a больше или равно b, в противном случае возвращает false. Вызывает Разрешение_типов_операндов для операндов.
(2 >= 1) == true
Меньше, чем
a < b
  • Целые_числа
  • Числа с плавающей точкой
Возвращает true если a меньше b, в противном случае возвращает false. Вызывает Разрешение_типов_операндов для операндов.
(1 < 2) == true
Меньше или равно
a <= b
  • Целые_числа
  • Числа с плавающей точкой
Возвращает true если a меньше или равно b, в противном случае возвращает false. Вызывает Разрешение_типов_операндов для операндов.
(1 <= 2) == true
Конкатенация массивов
a ++ b
  • Массивы
  • Доступно только тогда, когда длины обоих 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
  • Массивы
  • Доступно только тогда, когда длины 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);
}
Оболочка
$ 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.

См. также:

  • for
  • Срезы

Многомерные массивы

Многомерные массивы могут быть созданы путем вложенности массивов:

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

const mat4x4 = [4][4]f32{
    [_]f32{ 1.0, 0.0, 0.0, 0.0 },
    [_]f32{ 0.0, 1.0, 0.0, 1.0 },
    [_]f32{ 0.0, 0.0, 1.0, 0.0 },
    [_]f32{ 0.0, 0.0, 0.0, 1.0 },
};
test "multidimensional arrays" {
    // Access the 2D array by indexing the outer array, and then the inner array.
    try expect(mat4x4[1][1] == 1.0);

    // Here we iterate with for loops.
    for (mat4x4, 0..) |row, row_index| {
        for (row, 0..) |cell, column_index| {
            if (row_index == column_index) {
                try expect(cell == 1.0);
            }
        }
    }
}
Оболочка
$ zig test test_multidimensional_arrays.zig
1/1 test_multidimensional_arrays.test.multidimensional arrays...OK
All 1 tests passed.

Массивы_с_маркером_окончания

Синтаксис [N:x]T описывает массив, который имеет элемент-маркер со значением x в индексе, соответствующем длине N.

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);
}
Оболочка
$ 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.

См. также:

  • Указатели_с_маркером_окончания
  • Срезы_с_маркером_окончания

Векторы

Вектор представляет собой группу значений булевого типа, целых чисел, чисел с плавающей точкой или указателей, которые обрабатываются параллельно, используя инструкции SIMD, если это возможно. Типы векторов создаются с помощью встроенной функции @Вектор.

Векторы поддерживают те же встроенные операторы, что и их базовые типы. Эти операции выполняются поэлементно и возвращают вектор той же длины, что и входные векторы. Это включает:

  • Арифметические (+, -, /, *, @divFloor, @sqrt, @ceil, @log, и т.д.)
  • Битовые операторы (>>, <<, &, |, ~, и т.д.)
  • Операторы сравнения (<, >, ==, и т.д.)

Запрещается использовать математический оператор с комбинацией скаляров (отдельных чисел) и векторов. Zig предоставляет встроенную функцию @splat для удобного преобразования скаляров в векторы, а также поддерживает @reduce и синтаксис индексирования массивов для преобразования векторов в скаляры. Векторы также поддерживают присваивание из и в массивы фиксированной длины с известной на этапе компиляции длиной.

Для перестановки элементов внутри и между векторами Zig предоставляет функции @shuffle и @select.

Операции над векторами, длина которых короче, чем родной размер SIMD целевой машины, обычно компилируются в одну инструкцию SIMD, а векторы, длина которых длиннее, чем родной размер SIMD целевой машины, компилируются в несколько инструкций SIMD. Если для данной операции нет поддержки SIMD на целевой архитектуре, компилятор по умолчанию будет обрабатывать каждый элемент вектора по одному. Zig поддерживает любые известные на этапе компиляции длины векторов до 2^32-1, хотя наиболее типичными являются небольшие степени двойки (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);
}
Оболочка
$ 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

См. также:

  • @splat
  • @shuffle
  • @select
  • @reduce

Указатели

Zig имеет два типа указателей: указатели на один элемент и указатели на множество элементов.

  • *T - указатель на один элемент.
    • Поддерживает синтаксис разыменования: ptr.*
  • [*]T - указатель на множество элементов, количество которых неизвестно.
    • Поддерживает синтаксис индексирования: ptr[i]
    • Поддерживает синтаксис срезов: ptr[start..end] и ptr[start..]
    • Поддерживает арифметику указателей: ptr + x, ptr - x
    • T должен иметь известный размер, что означает, что он не может быть anyopaque или любым другим неявным типом.

Эти типы тесно связаны с массивами и срезами:

  • *[N]T - указатель на N элементов, такой же, как указатель на массив.
    • Поддерживает синтаксис индексирования: array_ptr[i]
    • Поддерживает синтаксис срезов: array_ptr[start..end]
    • Поддерживает свойство длины: array_ptr.len
  • []T - представляет собой срез (толстый указатель, который содержит указатель типа [*]T и длину).
    • Поддерживает синтаксис индексирования: slice[i]
    • Поддерживает синтаксис срезов: slice[start..end]
    • Поддерживает свойство длины: 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);
}
Оболочка
$ zig test test_single_item_pointer.zig
1/2 test_single_item_pointer.test.address of syntax...OK
2/2 test_single_item_pointer.test.pointer array access...OK
All 2 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);
}

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);
}
Оболочка
$ 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);
}
Оболочка
$ 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);
    }
}
Оболочка
$ 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);
}
Оболочка
$ 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);
    }
}
Оболочка
$ zig test test_comptime_pointer_conversion.zig
1/1 test_comptime_pointer_conversion.test.comptime @ptrFromInt...OK
All 1 tests passed.

См. также:

  • Необязательные_указатели
  • @ptrFromInt
  • @intFromPtr
  • Указатели_C

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);
}
Оболочка
$ zig test test_volatile.zig
1/1 test_volatile.test.volatile...OK
All 1 tests passed.

Обратите внимание, что volatile не связано с конкурентностью и атомарными операциями. Если вы видите код, использующий volatile для чего-то помимо отображения ввода/вывода в память, это, вероятно, ошибка.

@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);
}
Оболочка
$ 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.

Выравнивание

Каждый тип имеет выравнивание - количество байтов такое, что при загрузке или сохранении значения типа в память адрес памяти должен быть кратен этому числу. Вы можете использовать @alignOf, чтобы узнать это значение для любого типа.

Выравнивание зависит от архитектуры процессора, но всегда является степенью двойки и меньше, чем 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);
    }
}
Оболочка
$ 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);
}
Оболочка
$ 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];
}
Оболочка
$ zig test test_incorrect_pointer_alignment.zig
1/1 test_incorrect_pointer_alignment.test.pointer alignment safety...thread 3568823 panic: incorrect alignment
/home/andy/src/zig/doc/langref/test_incorrect_pointer_alignment.zig:10:68: 0x103d13a in foo (test)
    const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                                   ^
/home/andy/src/zig/doc/langref/test_incorrect_pointer_alignment.zig:6:31: 0x103cfd7 in test.pointer alignment safety (test)
    try std.testing.expect(foo(bytes) == 0x11111111);
                              ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x1047f10 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103e28b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d609 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103d171 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/c771705677c0d2df24e00269a9189f97/test

allowzero

Этот атрибут указателя позволяет указателю иметь адрес ноль. Это необходимо только для целевой системы без ОС, где адрес ноль может быть отображён. Если вы хотите представить нулевые указатели, используйте Дополнительные указатели вместо этого. Дополнительные указатели с allowzero имеют размер, отличный от размеров указателей. В этом примере кода, если у указателя не было атрибута allowzero, это была бы ошибка Преобразование указателя в недействительный нулевой:

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);
}
Оболочка
$ 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);
}
Оболочка
$ zig build-exe sentinel-terminated_pointer.zig -lc
/home/andy/src/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/src/zig/doc/langref/sentinel-terminated_pointer.zig:11:16: note: destination pointer requires '0' sentinel
/home/andy/src/zig/doc/langref/sentinel-terminated_pointer.zig:4:35: note: parameter type declared here
pub extern "c" fn printf(format: [*:0]const u8, ...) c_int;
                                 ~^~~~~~~~~~~~
referenced by:
    callMain: /home/andy/src/zig/lib/std/start.zig:524:32
    callMainWithArgs: /home/andy/src/zig/lib/std/start.zig:482:12
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

См. также:

  • Срезы, завершённые стоп-значением
  • Массивы, завершённые стоп-значением

Срезы

Срез — это указатель и длина. Разница между массивом и срезом заключается в том, что длина массива является частью типа и известна во время компиляции, а длина среза известна во время выполнения. Оба могут быть обработаны с помощью поля `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.
}
Оболочка
$ zig test test_basic_slices.zig
1/1 test_basic_slices.test.basic slices...thread 3571722 panic: index out of bounds: index 10, len 4
/home/andy/src/zig/doc/langref/test_basic_slices.zig:41:10: 0x103f955 in test.basic slices (test)
    slice[10] += 1;
         ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104c800 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x104210b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103fe49 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103f9b1 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/3c391d75c939ce98356b98dd812503b1/test

Это одна из причин, по которой мы предпочитаем срезы указателям.

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);
}
Оболочка
$ 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.

См. также:

  • Указатели
  • for
  • Массивы

Срезы, завершённые стоп-значением

Синтаксис [: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);
}
Оболочка
$ 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], где len — указатель, массив или срез, содержащий множество элементов, и 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);
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_sentinel_mismatch.zig
1/1 test_sentinel_mismatch.test.sentinel mismatch...thread 3579807 panic: sentinel mismatch: expected 0, found 1
/home/andy/src/zig/doc/langref/test_sentinel_mismatch.zig:13:24: 0x103cf16 in test.sentinel mismatch (test)
    const slice = array[0..runtime_length :0];
                       ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x1048aa0 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103eabb in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d4f9 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103d061 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/2af8da0d34d396fbb50fa515cef10c72/test

См. также:

  • Указатели, завершённые стоп-значением
  • Массивы, завершённые стоп-значением

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,
};

// Maybe we want to pass it to OpenGL so we want to be particular about
// how the bytes are arranged.
const Point2 = packed struct {
    x: f32,
    y: f32,
};

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

// Maybe we're not ready to fill out some of the fields.
var p2 = Point{
    .x = 0.12,
    .y = undefined,
};

// Structs can have methods
// Struct methods are not special, they are only namespaced
// functions that you can call 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;
    }
};

const expect = @import("std").testing.expect;
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);

    // you can still instantiate an empty struct
    const does_nothing = Empty{};

    _ = does_nothing;
}

// struct field order is determined by the compiler for optimal performance.
// however, you can still calculate a struct base pointer given 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);
}

// You can return a struct from a function. This is how we do generics
// in Zig:
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. This means you can
    // do this:
    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);
}
Оболочка
$ 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;
    }
}
Оболочка
$ 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;
Оболочка
$ zig build-exe bad_default_value.zig
$ ./bad_default_value
thread 3570319 panic: reached unreachable code
/home/andy/src/zig/lib/std/debug.zig:412:14: 0x1037a6d in assert (bad_default_value)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/src/zig/doc/langref/bad_default_value.zig:8:15: 0x1034f59 in categorize (bad_default_value)
        assert(t.maximum >= t.minimum);
              ^
/home/andy/src/zig/doc/langref/bad_default_value.zig:19:42: 0x1034e8a in main (bad_default_value)
    const category = threshold.categorize(0.90);
                                         ^
/home/andy/src/zig/lib/std/start.zig:524:37: 0x1034da5 in posixCallMainAndExit (bad_default_value)
            const result = root.main() catch |err| {
                                    ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10348c1 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 — лучший выбор, поскольку он накладывает меньше ограничений на компилятор.

См. packed struct для структуры с ABI своего базового целого числа, что может быть полезно для моделирования флагов.

См. также:

  • extern union
  • extern enum

packed struct

В отличие от обычных структур, packed структуры гарантируют представление в памяти:

  • Поля остаются в порядке объявления, от наименее к наиболее значимым.
  • Между полями нет заполнения.
  • Zig поддерживает целые числа произвольной длины, и хотя обычно целые числа с менее чем 8 битами все равно используют 1 байт памяти, в упакованных структурах они используют точно свою ширину в битах.
  • bool поля используют ровно 1 бит.
  • Поле перечисления использует точно ширину в битах своего целочисленного типа тега.
  • Поле упакованного объединения использует точно ширину в битах поля объединения с наибольшей шириной в битах.

Это означает, что 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);
        },
    }
}
Оболочка
$ 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 };
}
Оболочка
$ zig test test_missized_packed_struct.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 };
                            ^~~

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);
}
Оболочка
$ 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.*;
}
Оболочка
$ zig test test_misaligned_pointer.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);
                   ^~~~~~~~~~~~
doc/langref/test_misaligned_pointer.zig:17:20: note: pointer host size '1' cannot cast into pointer host size '0'
doc/langref/test_misaligned_pointer.zig:17:20: note: pointer bit offset '3' cannot cast into pointer bit offset '0'
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));
}
Оболочка
$ 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.

Это можно наблюдать с помощью @bitOffsetOf и offsetOf:

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);
    }
}
Оболочка
$ zig test test_bitOffsetOf_offsetOf.zig
1/1 test_bitOffsetOf_offsetOf.test.offsets of non-byte-aligned fields...OK
All 1 tests passed.

Упакованные структуры имеют тот же выравнивание, что и их базовые целые числа, однако, сверхвыровненные указатели на упакованные структуры могут переопределять это:

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);
}
Оболочка
$ 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));
}
Оболочка
$ zig test test_aligned_struct_fields.zig
1/1 test_aligned_struct_fields.test.aligned struct fields...OK
All 1 tests passed.

Использование упакованных структур с volatile проблематично и может стать ошибкой компиляции в будущем. Подробнее об этом см. данную проблему. TODO обновить эту документацию с рекомендацией по использованию упакованных структур с MMIO (случаи использования volatile упакованных структур), как только эта проблема будет решена. Не волнуйтесь, для этого случая в 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,
    };
}
Оболочка
$ zig build-exe struct_name.zig
$ ./struct_name
variable: struct_name.main.Foo
anonymous: struct_name.main__struct_3331
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);
}
Оболочка
$ 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');
}
Оболочка
$ zig test test_anonymous_struct.zig
1/1 test_anonymous_struct.test.fully anonymous struct...OK
All 1 tests passed.

Кортежи

Анонимные структуры могут быть созданы без указания имён полей и называются "кортежами".

Поля неявно называются числами, начиная с 0. Поскольку их имена — целые числа, к ним нельзя получить доступ с помощью . синтаксиса без обертывания их в @"". Имена внутри @"" всегда распознаются как идентификаторы.

Как и массивы, кортежи имеют поле .len, могут быть индексированы (при условии, что индекс известен во время компиляции) и работают с операторами ++ и **. Они также могут быть перебираемы с помощью inline 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');
}
Оболочка
$ zig test test_tuples.zig
1/1 test_tuples.test.tuple...OK
All 1 tests passed.

См. также:

  • comptime
  • @fieldParentPtr

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"));
}
Оболочка
$ 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.

См. также:

  • @typeInfo
  • @tagName
  • @sizeOf

extern enum

По умолчанию перечисления не гарантируют совместимость с C ABI:

enum_export_error.zig
const Foo = enum { a, b, c };
export fn entry(foo: Foo) void {
    _ = foo;
}
Оболочка
$ zig build-obj enum_export_error.zig
doc/langref/enum_export_error.zig:2:17: error: parameter of type 'enum_export_error.Foo' not allowed in function with calling convention 'C'
export fn entry(foo: Foo) void {
                ^~~~~~~~
doc/langref/enum_export_error.zig:2:17: note: enum tag type 'u2' is not extern compatible
doc/langref/enum_export_error.zig:2:17: note: only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible
doc/langref/enum_export_error.zig:1:13: note: enum declared here
const Foo = enum { a, b, c };
            ^~~~~~~~~~~~~~~~

Для совместимости с C ABI укажите явный тип тега для перечисления:

enum_export.zig
const Foo = enum(c_int) { a, b, c };
export fn entry(foo: Foo) void {
    _ = foo;
}
Оболочка
$ 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);
}
Оболочка
$ 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.

Неполное перечисление

Неполное перечисление можно создать, добавив поле с trailing _. Перечисление должно указать тип тега и не может использовать все значения перечисления.

@enumFromInt для неполного перечисления включает семантику безопасности @intCast для целочисленного типа тега, но в дальнейшем всегда приводит к определенному значению перечисления.

Переключатель для неполного перечисления может включать _ разветвление в качестве альтернативы else разветвлению. С _ разветвлением компилятор выдаёт ошибку, если все известные имена тегов не обрабатываются переключателем.

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);
}
Оболочка
$ 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 union или packed 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;
}
Оболочка
$ zig test test_wrong_union_access.zig
1/1 test_wrong_union_access.test.simple union...thread 3579408 panic: access of union field 'float' while field 'int' is active
/home/andy/src/zig/doc/langref/test_wrong_union_access.zig:8:12: 0x103ce87 in test.simple union (test)
    payload.float = 12.34;
           ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x1048070 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103e08b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d419 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103cf81 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/bb9968225995fac0bbc9f2116e8583c2/test

Вы можете активировать другое поле, присвоив всё объединение:

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);
}
Оболочка
$ zig test test_simple_union.zig
1/1 test_simple_union.test.simple union...OK
All 1 tests passed.

Для использования switch с объединением оно должно быть меченым объединением.

Для инициализации объединения, когда тег — известное во время компиляции имя, см. @unionInit.

Меченые объединения

Объединения могут быть объявлены с типом тега перечисления. Это превращает объединение в меченное объединение, что делает его пригодным для использования с выражениями 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) {
        ComplexTypeTag.ok => |value| try expect(value == 42),
        ComplexTypeTag.not_ok => unreachable,
    }
}

test "get tag type" {
    try expect(std.meta.Tag(ComplexType) == ComplexTypeTag);
}
Оболочка
$ 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);
}
Оболочка
$ 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 };

    try expect(v1.truthy());
    try expect(!v2.truthy());
}
Оболочка
$ zig test test_union_method.zig
1/1 test_union_method.test.union method...OK
All 1 tests passed.

@tagName может использоваться для возвращения comptime [: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"));
}
Оболочка
$ zig test test_tagName.zig
1/1 test_tagName.test.@tagName...OK
All 1 tests passed.

extern union

extern union имеет гарантированную структуру памяти, совместимую с целевым C ABI.

См. также:

  • extern struct

packed union

У packed union есть чётко определённое расположение в памяти и оно может быть в упакованной структуре.

Анонимные литералы объединений

Анонимные литералы структур синтаксис может быть использован для инициализации объединений без указания типа:

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 };
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_opaque.zig
doc/langref/test_opaque.zig:6:9: error: expected type '*test_opaque.Derp', found '*test_opaque.Wat'
    bar(w);
        ^
doc/langref/test_opaque.zig:6:9: note: pointer type child 'test_opaque.Wat' cannot cast into pointer type child 'test_opaque.Derp'
doc/langref/test_opaque.zig:2:13: note: opaque declared here
const Wat = opaque {};
            ^~~~~~~~~
doc/langref/test_opaque.zig:1:14: note: opaque declared here
const Derp = opaque {};
             ^~~~~~~~~
doc/langref/test_opaque.zig:4:18: note: parameter type declared here
extern fn bar(d: *Derp) void;
                 ^~~~~
referenced by:
    test.call foo: doc/langref/test_opaque.zig:10:5
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

Блоки

Блоки используются для ограничения области видимости объявлений переменных:

test_blocks.zig
test "access variable after block scope" {
    {
        var x: i32 = 1;
        _ = &x;
    }
    x += 1;
}
Оболочка
$ zig test test_blocks.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);
}
Оболочка
$ zig test test_labeled_break.zig
1/1 test_labeled_break.test.labeled break from labeled block expression...OK
All 1 tests passed.

Здесь blk может быть любым именем.

См. также:

  • Цикл while с меткой
  • Цикл for с меткой

Затенение

Идентификаторы никогда не допускается «скрывать» другие идентификаторы, используя то же имя:

test_shadowing.zig
const pi = 3.14;

test "inside test block" {
    // Let's even go inside another block
    {
        var pi: i32 = 1234;
    }
}
Оболочка
$ zig test test_shadowing.zig
doc/langref/test_shadowing.zig:6:13: error: local variable shadows declaration of 'pi'
        var pi: i32 = 1234;
            ^~
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;
    }
}
Оболочка
$ 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);
}
Оболочка
$ 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 => {},
    }
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_switch_tagged_union.zig
1/1 test_switch_tagged_union.test.switch on tagged union...OK
All 1 tests passed.

См. также:

  • comptime
  • перечисление
  • @compileError
  • Переменные компиляции

Исчерпывающее переключение

Когда выражение 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 => {},
    }
}
Оболочка
$ zig test test_unhandled_enumeration_value.zig
doc/langref/test_unhandled_enumeration_value.zig:9:5: error: switch must handle all possibilities
    switch (color) {
    ^~~~~~
doc/langref/test_unhandled_enumeration_value.zig:3:5: note: unhandled enumeration value: 'off'
    off,
    ^~~
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);
}
Оболочка
$ zig test test_exhaustive_switch.zig
1/1 test_exhaustive_switch.test.enum literals with switch...OK
All 1 tests passed.

Встроенные ветви switch

Ветви switch могут быть помечены как inline для генерации тела ветви для каждого возможного значения, что делает захваченное значение comptime.

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,
    };
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_inline_else.zig
1/1 test_inline_else.test.inline for and inline else similarity...OK
All 1 tests passed.

При использовании встроенной ветви switch по объединению можно использовать дополнительный захват для получения значения тега перечисления объединения.

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);
}
Оболочка
$ zig test test_inline_switch_union_tag.zig
1/1 test_inline_switch_union_tag.test.test...OK
All 1 tests passed.

См. также:

  • Встроенный цикл while
  • Встроенный цикл for

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);
}
Оболочка
$ 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);
}
Оболочка
$ 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);
}
Оболочка
$ 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);
    }
}
Оболочка
$ 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 являются выражениями. Результатом выражения является результат else раздела цикла while, который выполняется, когда условие цикла while проверяется как ложное.

break, как и return, принимает параметр значения. Это результат while выражения. Когда вы break из цикла while, 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;
}
Оболочка
$ zig test test_while_else.zig
1/1 test_while_else.test.while else...OK
All 1 tests passed.

Цикл while с меткой

Когда цикл while имеет метку, к нему можно обратиться из break или continue внутри вложенного цикла:

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;
        }
    }
}
Оболочка
$ 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.

Цикл while с необязательными значениями

Так же, как выражения if, циклы while могут принимать необязательное значение в качестве условия и захватывать полезную нагрузку. При встрече null цикл завершается.

Когда синтаксис |x| присутствует в while выражении, условие цикла 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;
    };
}
Оболочка
$ zig test test_while_null_capture.zig
1/1 test_while_null_capture.test.while null capture...OK
All 1 tests passed.

Цикл while с объединениями ошибок

Так же, как выражения if, циклы while могут принимать объединение ошибок в качестве условия и захватывать полезную нагрузку или код ошибки. Когда условие приводит к коду ошибки, выполняется раздел else, и цикл завершается.

Когда синтаксис else |x| присутствует в while выражении, условие цикла 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;
    };
}
Оболочка
$ zig test test_while_error_capture.zig
1/1 test_while_error_capture.test.while error union capture...OK
All 1 tests passed.

Встроенный цикл 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;
}
Оболочка
$ zig test test_inline_while.zig
1/1 test_inline_while.test.inline while loop...OK
All 1 tests passed.

Рекомендуется использовать встроенные циклы только по одной из следующих причин:

  • Вам нужен цикл, который выполняется во время comptime для работы семантики.
  • У вас есть бенчмарк, доказывающий, что принудительное развёртывание цикла таким образом измеряемо быстрее.

См. также:

  • if
  • Необязательные значения
  • Ошибки
  • comptime
  • unreachable

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);
}
Оболочка
$ 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 имеет метку, к нему можно обратиться из break или continue внутри вложенного цикла:

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);
}
Оболочка
$ 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.

Встроенный цикл 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;
}
Оболочка
$ zig test test_inline_for.zig
1/1 test_inline_for.test.inline for loop...OK
All 1 tests passed.

Рекомендуется использовать встроенные циклы только по одной из следующих причин:

  • Вам нужен цикл, который выполняется во время comptime для работы семантики.
  • У вас есть бенчмарк, доказывающий, что принудительное развёртывание цикла таким образом измеряемо быстрее.

См. также:

  • while
  • comptime
  • Массивы
  • Срезы

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;
    }
}
Оболочка
$ 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.

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;
    }
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_defer.zig
1/1 test_defer.test.defer basics...OK
All 1 tests passed.

Выражения defer вычисляются в обратном порядке.

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 ", .{});
        }
    }
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_invalid_defer.zig
doc/langref/test_invalid_defer.zig:3:9: error: cannot return from defer expression
        return error.DeferError;
        ^~~~~~~~~~~~~~~~~~~~~~~
doc/langref/test_invalid_defer.zig:2:5: note: defer expression here
    defer {
    ^~~~~

См. также:

  • Ошибки

unreachable

В режиме Debug и ReleaseSafe unreachable вызывает panic с сообщением reached unreachable code.

В режиме ReleaseFast и ReleaseSmall оптимизатор использует предположение, что 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;
    }
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_assertion_failure.zig
1/1 test_assertion_failure.test.this will fail...thread 3571599 panic: reached unreachable code
/home/andy/src/zig/doc/langref/test_assertion_failure.zig:3:14: 0x103cd9d in assert (test)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/src/zig/doc/langref/test_assertion_failure.zig:8:11: 0x103cd5a in test.this will fail (test)
    assert(false);
          ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x10479a0 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103dbbb in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d249 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103cdb1 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/a6b3ce5875a9e285c15739b2a1b30733/test

На этапе компиляции

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);
    }
}
Оболочка
$ zig test test_comptime_unreachable.zig
doc/langref/test_comptime_unreachable.zig:10:16: error: unreachable code
        assert(@TypeOf(unreachable) == noreturn);
               ^~~~~~~~~~~~~~~~~~~~
doc/langref/test_comptime_unreachable.zig:10:24: note: control flow is diverted here
        assert(@TypeOf(unreachable) == noreturn);
                       ^~~~~~~~~~~

См. также:

  • Тест Zig
  • Режим сборки
  • comptime

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);
}
Оболочка
$ 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;
}
Оболочка
$ 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 @setCold builtin tells the optimizer that a function is rarely called.
fn abort() noreturn {
    @setCold(true);
    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);
}
Оболочка
$ zig test test_functions.zig
1/1 test_functions.test.function...OK
All 1 tests passed.

Существует различие между телом функции и указателем на функцию. Тела функций — это типы только для comptime, в то время как указатели на функции могут быть известны во время выполнения.

Параметры, передаваемые по значению

Примитивные типы, такие как целые числа и числа с плавающей точкой, передаваемые в качестве параметров, копируются, а затем копия доступна в теле функции. Это называется "передачей по значению". Копирование примитивного типа практически бесплатно и обычно включает лишь установку регистра.

Структуры, объединения и массивы иногда могут быть более эффективно переданы по ссылке, так как копия может быть произвольно дорогой, в зависимости от размера. При передаче этих типов в качестве параметров 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);
}
Оболочка
$ 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);
}
Оболочка
$ 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;
}
Оболочка
$ 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);
}
Оболочка
$ zig test test_fn_reflection.zig
1/1 test_fn_reflection.test.fn reflection...OK
All 1 tests passed.

Ошибки

Тип множества ошибок

Множество ошибок похоже на перечисление. Однако каждому имени ошибки во всей компиляции присваивается целое число без знака, большее 0. Разрешено объявлять одно и то же имя ошибки более одного раза; в этом случае ему присваивается то же значение целого числа.

Тип множества ошибок по умолчанию — u16, хотя если максимальное количество различных значений ошибок задано через параметр командной строки --error-limit [число], будет использоваться целочисленный тип с минимальным числом битов, необходимым для представления всех значений ошибок.

Можно преобразовать ошибку из подмножества в надмножество:

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;
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_coerce_error_superset_to_subset.zig
doc/langref/test_coerce_error_superset_to_subset.zig:16:12: error: expected type 'error{OutOfMemory}', found 'error{AccessDenied,OutOfMemory,FileNotFound}'
    return err;
           ^~~
doc/langref/test_coerce_error_superset_to_subset.zig:16:12: note: 'error.AccessDenied' not a member of destination error set
doc/langref/test_coerce_error_superset_to_subset.zig:16:12: note: 'error.FileNotFound' not a member of destination error set
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: doc/langref/test_coerce_error_superset_to_subset.zig:12:5
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

Существует сокращение для объявления множества ошибок только с 1 значением и получения этого значения:

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);
}
Оболочка
$ zig test error_union_parsing_u64.zig
1/1 error_union_parsing_u64.test.parse u64...OK
All 1 tests passed.

Обратите внимание, что возвращаемый тип — !u64. Это означает, что функция либо возвращает целое число с 64 битами без знака, либо ошибку. Мы убрали множество ошибок слева от !, поэтому множество ошибок выводится.

В определении функции вы видите несколько операторов return, которые возвращают ошибку, а внизу оператор return, который возвращает u64. Оба типа преобразуются в 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 вызывает панику в режимах Debug и ReleaseSafe и неопределённое поведение в режимах ReleaseFast и ReleaseSmall. Таким образом, при отладке приложения, если здесь возникнет неожиданная ошибка, приложение завершится соответствующим образом.

Вы можете захотеть выполнить разные действия в разных ситуациях. Для этого мы объединяем выражение if и switch:

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

Следует отметить, что инструкции errdefer действуют только до конца блока, в котором они объявлены, и, следовательно, не выполняются, если ошибка возвращается за пределами этого блока:

test_errdefer_slip_ups.zig
const std = @import("std");
const Allocator = std.mem.Allocator;

const Foo = struct {
    data: u32,
};

fn tryToAllocateFoo(allocator: Allocator) !*Foo {
    return allocator.create(Foo);
}

fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
    allocator.destroy(foo);
}

fn getFooData() !u32 {
    return 666;
}

fn createFoo(allocator: Allocator, param: i32) !*Foo {
    const foo = getFoo: {
        var foo = try tryToAllocateFoo(allocator);
        errdefer deallocateFoo(allocator, foo); // Only lasts until the end of getFoo

        // Calls deallocateFoo on error
        foo.data = try getFooData();

        break :getFoo foo;
    };

    // Outside of the scope of the errdefer, so
    // deallocateFoo will not be called here
    if (param > 1337) return error.InvalidParam;

    return foo;
}

test "createFoo" {
    try std.testing.expectError(error.InvalidParam, createFoo(std.testing.allocator, 2468));
}
Shell
$ zig test test_errdefer_slip_ups.zig
1/1 test_errdefer_slip_ups.test.createFoo...OK
[gpa] (err): memory address 0x7f11f521a000 leaked:
/home/andy/src/zig/doc/langref/test_errdefer_slip_ups.zig:9:28: 0x103d3cf in tryToAllocateFoo (test)
    return allocator.create(Foo);
                           ^
/home/andy/src/zig/doc/langref/test_errdefer_slip_ups.zig:22:39: 0x103d5e5 in createFoo (test)
        var foo = try tryToAllocateFoo(allocator);
                                      ^
/home/andy/src/zig/doc/langref/test_errdefer_slip_ups.zig:39:62: 0x103d82d in test.createFoo (test)
    try std.testing.expectError(error.InvalidParam, createFoo(std.testing.allocator, 2468));
                                                             ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104d2a0 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x104340b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103f9f9 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103f561 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/src/zig/.zig-cache/o/58c079cb550addefaa354f72d736afd7/test

Чтобы убедиться, что deallocateFoo вызывается при возврате ошибки, вы должны добавить errdefer за пределами блока:

test_errdefer_block.zig
const std = @import("std");
const Allocator = std.mem.Allocator;

const Foo = struct {
    data: u32,
};

fn tryToAllocateFoo(allocator: Allocator) !*Foo {
    return allocator.create(Foo);
}

fn deallocateFoo(allocator: Allocator, foo: *Foo) void {
    allocator.destroy(foo);
}

fn getFooData() !u32 {
    return 666;
}

fn createFoo(allocator: Allocator, param: i32) !*Foo {
    const foo = getFoo: {
        var foo = try tryToAllocateFoo(allocator);
        errdefer deallocateFoo(allocator, foo);

        foo.data = try getFooData();

        break :getFoo foo;
    };
    // This lasts for the rest of the function
    errdefer deallocateFoo(allocator, foo);

    // Error is now properly handled by errdefer
    if (param > 1337) return error.InvalidParam;

    return foo;
}

test "createFoo" {
    try std.testing.expectError(error.InvalidParam, createFoo(std.testing.allocator, 2468));
}
Shell
$ zig test test_errdefer_block.zig
1/1 test_errdefer_block.test.createFoo...OK
All 1 tests passed.

Факт, что errdefer действуют только в пределах блока, особенно важен при использовании циклов:

test_errdefer_loop_leak.zig
const std = @import("std");
const Allocator = std.mem.Allocator;

const Foo = struct { data: *u32 };

fn getData() !u32 {
    return 666;
}

fn genFoos(allocator: Allocator, num: usize) ![]Foo {
    const foos = try allocator.alloc(Foo, num);
    errdefer allocator.free(foos);

    for (foos, 0..) |*foo, i| {
        foo.data = try allocator.create(u32);
        // This errdefer does not last between iterations
        errdefer allocator.destroy(foo.data);

        // The data for the first 3 foos will be leaked
        if (i >= 3) return error.TooManyFoos;

        foo.data.* = try getData();
    }

    return foos;
}

test "genFoos" {
    try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
}
Shell
$ zig test test_errdefer_loop_leak.zig
1/1 test_errdefer_loop_leak.test.genFoos...OK
[gpa] (err): memory address 0x7f57c7578000 leaked:
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:15:40: 0x103d7a6 in genFoos (test)
        foo.data = try allocator.create(u32);
                                       ^
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:29:59: 0x103e0dd in test.genFoos (test)
    try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
                                                          ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104e010 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x1043eab in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x10402a9 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103fe11 in _start (test)
    asm volatile (switch (native_arch) {
    ^

[gpa] (err): memory address 0x7f57c7578004 leaked:
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:15:40: 0x103d7a6 in genFoos (test)
        foo.data = try allocator.create(u32);
                                       ^
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:29:59: 0x103e0dd in test.genFoos (test)
    try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
                                                          ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104e010 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x1043eab in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x10402a9 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103fe11 in _start (test)
    asm volatile (switch (native_arch) {
    ^

[gpa] (err): memory address 0x7f57c7578008 leaked:
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:15:40: 0x103d7a6 in genFoos (test)
        foo.data = try allocator.create(u32);
                                       ^
/home/andy/src/zig/doc/langref/test_errdefer_loop_leak.zig:29:59: 0x103e0dd in test.genFoos (test)
    try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
                                                          ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x104e010 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x1043eab in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x10402a9 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103fe11 in _start (test)
    asm volatile (switch (native_arch) {
    ^

All 1 tests passed.
3 errors were logged.
1 tests leaked memory.
error: the following test command failed with exit code 1:
/home/andy/src/zig/.zig-cache/o/29fcda275b7c426418534b679850fa2e/test

При работе с кодом, который выделяет память в цикле, необходимо уделить особое внимание, чтобы избежать утечки памяти при возврате ошибки:

test_errdefer_loop.zig
const std = @import("std");
const Allocator = std.mem.Allocator;

const Foo = struct { data: *u32 };

fn getData() !u32 {
    return 666;
}

fn genFoos(allocator: Allocator, num: usize) ![]Foo {
    const foos = try allocator.alloc(Foo, num);
    errdefer allocator.free(foos);

    // Used to track how many foos have been initialized
    // (including their data being allocated)
    var num_allocated: usize = 0;
    errdefer for (foos[0..num_allocated]) |foo| {
        allocator.destroy(foo.data);
    };
    for (foos, 0..) |*foo, i| {
        foo.data = try allocator.create(u32);
        num_allocated += 1;

        if (i >= 3) return error.TooManyFoos;

        foo.data.* = try getData();
    }

    return foos;
}

test "genFoos" {
    try std.testing.expectError(error.TooManyFoos, genFoos(std.testing.allocator, 5));
}
Shell
$ zig test test_errdefer_loop.zig
1/1 test_errdefer_loop.test.genFoos...OK
All 1 tests passed.

Несколько дополнительных моментов по обработке ошибок:

  • Эти примитивы обеспечивают достаточную выразительность, что пропуск проверки на ошибку может стать ошибкой компиляции. Если вы действительно хотите проигнорировать ошибку, вы можете добавить catch unreachable и получить дополнительное преимущество — завершение программы с ошибкой в режимах Debug и ReleaseSafe, если ваше предположение было неверным.
  • Поскольку Zig понимает типы ошибок, он может преднастроить ветви в пользу того, что ошибки не возникнут. Это небольшое оптимизационное преимущество, недоступное в других языках.

См. также:

  • defer
  • if
  • switch

Объединение объединений ошибок создается с помощью бинарного оператора !. Вы можете использовать рефлексию на этапе компиляции для доступа к типу дочернего элемента объединения ошибок:

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)).ErrorUnion.payload == i32);

    // Use compile-time reflection to access the error set type of an error union:
    try comptime expect(@typeInfo(@TypeOf(foo)).ErrorUnion.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.

Это особенно полезно для функций, возвращающих разные наборы ошибок в зависимости от comptime ветвей. Например, стандартная библиотека 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/src/zig/doc/langref/error_return_trace.zig:34:5: 0x1034e08 in bang1 (error_return_trace)
    return error.FileNotFound;
    ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:22:5: 0x1034f13 in baz (error_return_trace)
    try bang1();
    ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:38:5: 0x1034f38 in bang2 (error_return_trace)
    return error.PermissionDenied;
    ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:30:5: 0x1034fa3 in hello (error_return_trace)
    try bang2();
    ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:17:31: 0x103505a in bar (error_return_trace)
        error.FileNotFound => try hello(),
                              ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:7:9: 0x1035140 in foo (error_return_trace)
        try bar();
        ^
/home/andy/src/zig/doc/langref/error_return_trace.zig:2:5: 0x1035198 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 3570764 panic: PermissionDenied
/home/andy/src/zig/doc/langref/stack_trace.zig:38:5: 0x1039320 in bang2 (stack_trace)
    @panic("PermissionDenied");
    ^
/home/andy/src/zig/doc/langref/stack_trace.zig:30:10: 0x1068bd8 in hello (stack_trace)
    bang2();
         ^
/home/andy/src/zig/doc/langref/stack_trace.zig:17:14: 0x10392fc in bar (stack_trace)
        hello();
             ^
/home/andy/src/zig/doc/langref/stack_trace.zig:7:12: 0x103721c in foo (stack_trace)
        bar();
           ^
/home/andy/src/zig/doc/langref/stack_trace.zig:2:8: 0x103519d in main (stack_trace)
    foo(12);
       ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034a49 in posixCallMainAndExit (stack_trace)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10345b1 in _start (stack_trace)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Здесь стек вызовов не объясняет, как поток управления в bar добрался до вызова hello(). Для этого пришлось бы открыть отладчик или дополнительно инструментировать приложение, чтобы это выяснить. Сведения об ошибках возврата, с другой стороны, точно показывают, как ошибка поднялась вверх.

Эта функция отладки упрощает быструю итерацию над кодом, который надёжно обрабатывает все условия ошибок. Это означает, что разработчики Zig будут естественным образом писать правильный и надёжный код, чтобы повысить скорость разработки.

Сведения об ошибках возврата включены по умолчанию в сборках Debug и ReleaseSafe и отключены по умолчанию в сборках ReleaseFast и ReleaseSmall.

Есть несколько способов включить эту функцию отслеживания ошибок возврата:

  • Возврат ошибки из main
  • Ошибка попадает в catch unreachable и вы не переопределили обработчик паники по умолчанию
  • Использование 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 математические операции плюс некоторые чтение и запись в памяти. Доступ к памяти ограничен и должен оставаться в кэше в течение всего времени возвращения ошибки.

Что касается стоимости размера кода, 1 вызов функции перед оператором возврата — это не проблема. Тем не менее, у меня есть план по преобразованию вызова __zig_return_error в хвостовой вызов, что снижает стоимость размера кода до нуля. Оператор возврата в коде без отслеживания возврата ошибок может стать инструкцией перехода в коде с отслеживанием возврата ошибок.

Необязательные значения

Одна область, в которой 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 не ?[*]u8. Ключевое слово orelse распаковывает необязательный тип, и поэтому 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" — __attribute__((nonnull)) в GCC. Оптимизатор иногда может принимать лучшие решения, зная, что аргументы-указатели не могут быть 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);
}
Оболочка
$ 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;

Необязательные указатели

Необязательный указатель гарантированно имеет тот же размер, что и указатель. Адрес необязательного указателя гарантированно равен 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));
}
Оболочка
$ zig test test_optional_pointer.zig
1/1 test_optional_pointer.test.optional pointers...OK
All 1 tests passed.

См. также:

  • while с необязательными значениями
  • if с необязательными значениями

Приведение типов

Приведение типов преобразует значение одного типа в другой. 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;
}
Оболочка
$ 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 — разрешено преобразование из non-const в const
  • volatile — разрешено преобразование из non-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 {}
Оболочка
$ zig test test_no_op_casts.zig
1/1 test_no_op_casts.test.type coercion - const qualification...OK
All 1 tests passed.

Кроме того, указатели приводятся к константным необязательным указателям:

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

test "cast *[1][*]const u8 to [*]const ?[*]const u8" {
    const window_name = [1][*]const u8{"window name"};
    const x: [*]const ?[*]const u8 = &window_name;
    try expect(mem.eql(u8, std.mem.sliceTo(@as([*:0]const u8, @ptrCast(x[0].?)), 0), "window name"));
}
Оболочка
$ zig test test_pointer_coerce_const_optional.zig
1/1 test_pointer_coerce_const_optional.test.cast *[1][*]const u8 to [*]const ?[*]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);
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_ambiguous_coercion.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;
                   ~~~~~^~~

Приведение типов: Сlices, массивы и указатели

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);
}
Оболочка
$ 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.

См. также:

  • указатели C

Приведение типов: Необязательные значения

Тип данных содержимого необязательных значений, а также 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);
}
Оболочка
$ 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);
}
Оболочка
$ 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);
}
Оболочка
$ 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);
}
Оболочка
$ 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:

END_OF_DOCUMENT_MARKER
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);
}
Оболочка
$ 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 homogenous tuple to array" {
    const tuple: Tuple = .{ 5, 6 };
    const array: [2]u8 = tuple;
    _ = array;
}
Оболочка
$ zig test test_coerce_tuples_arrays.zig
1/1 test_coerce_tuples_arrays.test.coercion from homogenous tuple to array...OK
All 1 tests passed.

Явные преобразования типов

Явные преобразования типов выполняются с помощью встроенных функций. Некоторые явные преобразования безопасны; некоторые — нет. Некоторые явные преобразования выполняют утверждения на уровне языка; некоторые — нет. Некоторые явные преобразования являются пустыми операциями во время выполнения; некоторые — нет.

  • @bitCast — изменение типа, но сохранение битовой записи
  • @alignCast — увеличение выравнивания указателя
  • @enumFromInt — получение значения перечисления на основе его целочисленного тега
  • @errorFromInt — получение кода ошибки на основе его целочисленного значения
  • @errorCast — преобразование в меньший набор ошибок
  • @floatCast — преобразование большего числа с плавающей точкой в меньшее
  • @floatFromInt — преобразование целого числа в значение с плавающей точкой
  • @intCast — преобразование между целочисленными типами
  • @intFromBool — преобразование true в 1, а false в 0
  • @intFromEnum — получение целочисленного значения тега перечисления или помеченного объединения
  • @intFromError — получение целочисленного значения кода ошибки
  • @intFromFloat — получение целой части значения с плавающей точкой
  • @intFromPtr — получение адреса указателя
  • @ptrFromInt — преобразование адреса в указатель
  • @ptrCast — преобразование между типами указателей
  • @truncate — преобразование между целочисленными типами, отбрасывая биты

Разрешение типов-собратьев

Разрешение типов-собратьев происходит в следующих местах:

  • выражения switch
  • выражения if
  • выражения while
  • выражения for
  • Несколько операторов break в блоке
  • Некоторые бинарные операции

Этот вид разрешения типов выбирает тип, в который все типы-собратьев могут быть приведены. Вот некоторые примеры:

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);
}
Оболочка
$ 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:

  • void
  • Целочисленные типы Целочисленные типы u0 и i0.
  • Массивы и вектора с длиной 0 или с типом элемента, являющимся типом с нулевой разрядностью.
  • Перечисление перечисление только с 1 тегом.
  • Структура со всеми полями, являющимися типами с нулевой разрядностью.
  • Объединение только с 1 полем, являющимся типом с нулевой разрядностью.

Эти типы могут иметь только одно возможное значение и, следовательно, требуют 0 бит для представления. Код, использующий эти типы, не включён в конечный сгенерированный код:

zero_bit_types.zig
export fn entry() void {
    var x: void = {};
    var y: void = {};
    x = y;
    y = x;
}

При преобразовании в машинный код в теле entry, даже в режиме Debug, не генерируется никакой код. Например, на 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));
}
Оболочка
$ 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 в качестве типа значения, тип записи хеш-таблицы не имеет поля значения, а значит хеш-таблица занимает меньше места. Кроме того, весь код, связанный с хранением и загрузкой значения, удаляется, как показано выше.

void отличается от anyopaque. void имеет известный размер 0 байт, а anyopaque имеет неизвестный, но не нулевой размер.

Выражения типа void — единственные, чьё значение может быть проигнорировано. Например, игнорирование не-void выражения является ошибкой компиляции:

test_expression_ignored.zig
test "ignoring expression value" {
    foo();
}

fn foo() i32 {
    return 1234;
}
Оболочка
$ zig test test_expression_ignored.zig
doc/langref/test_expression_ignored.zig:2:8: error: value of type 'i32' ignored
    foo();
    ~~~^~
doc/langref/test_expression_ignored.zig:2:8: note: all non-void values must be used
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;
}
Оболочка
$ 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;. Аннотация типа здесь предоставляет тип результата u32 для выражения инициализации 42, указывая компилятору привести это целое число (изначально типа 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);
}
Оболочка
$ zig test result_type_propagation.zig
1/1 result_type_propagation.test.result type propagates through struct initializer...OK
All 1 tests passed.

Эта информация о типе результата полезна для вышеупомянутых встроенных функций приведения типа, а также для избежания создания значений до приведения, а также для избежания необходимости явного приведения типов в некоторых случаях. В следующей таблице подробно описывается, как некоторые распространённые выражения распространяют типы результатов, где x и y — произвольные подвыражения.

Выражение Родительский тип результата Тип результата подвыражения
const val: T = x - x является T
var val: T = x - x является T
val = x - x является @TypeOf(val)
@as(T, x) - x является T
&x *T x является T
&x []T x является массивом T
f(x) - x имеет тип первого параметра f
.{x} T x является std.meta.FieldType(T, .@"0")
.{ .a = x } T x является std.meta.FieldType(T, .a)
T{x} - x является std.meta.FieldType(T, .@"0")
T{ .a = x } - x является std.meta.FieldType(T, .a)
@Type(x) - x является std.builtin.Type
@typeInfo(x) - x является type
x << y - y является std.math.Log2IntCeil(@TypeOf(x))

Расположения результатов

Помимо информации о типе результата, каждое выражение может быть необязательно назначено местоположению результата: указателю, в который значение должно быть записано напрямую. Эта система может использоваться для предотвращения промежуточных копий при инициализации структур данных, что может быть важным для типов, которые должны иметь фиксированный адрес памяти («прикреплённые» типы).

При компиляции простого выражения присваивания x = e, многие языки создадут временное значение e в стеке, а затем присвоят его x, потенциально выполнив при этом преобразование типов. Zig подходит к этому по-другому. Выражению e присваивается тип результата, соответствующий типу x, и местоположение результата &x. Для многих синтаксических форм e, это не оказывает практического влияния. Однако это может иметь важное семантическое влияние при работе с более сложными синтаксическими формами.

Например, если выражение .{ .a = x, .b = y } имеет местоположение результата ptr, то x получает местоположение результата &ptr.a, а y — местоположение результата &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
}
Оболочка
$ 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/src/zig/lib/std/testing.zig:540:14: 0x103ce1f in expect (test)
    if (!ok) return error.TestUnexpectedResult;
             ^
/home/andy/src/zig/doc/langref/result_location_interfering_with_swap.zig:10:5: 0x103cf85 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/src/zig/.zig-cache/o/c42c6019fdf548f70655aafe3673a46e/test

В следующей таблице подробно показано, как некоторые распространённые выражения распространяют местоположения результатов, где x и y являются произвольными подвыражениями. Обратите внимание, что некоторые выражения не могут предоставить осмысленные местоположения результатов подвыражениям, даже если у них есть местоположение результата.

Выражение Местоположение результата Местоположения результатов подвыражений
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 x и y не имеют местоположений результатов

usingnamespace

usingnamespace — это объявление, которое объединяет все публичные объявления операнда, который должен быть структурой, объединением, перечислением или непрозрачным типом, в пространство имён:

test_usingnamespace.zig
test "using std namespace" {
    const S = struct {
        usingnamespace @import("std");
    };
    try S.testing.expect(true);
}
Оболочка
$ zig test test_usingnamespace.zig
1/1 test_usingnamespace.test.using std namespace...OK
All 1 tests passed.

usingnamespace имеет важное применение при организации публичного API файла или пакета. Например, можно иметь c.zig со всеми импортами из C:

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;
}
Оболочка
$ zig test test_unresolved_comptime_value.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);
                           ^~~~~~~~~
doc/langref/test_unresolved_comptime_value.zig:8:28: note: condition in comptime branch must be comptime-known
referenced by:
    test.try to pass a runtime type: doc/langref/test_unresolved_comptime_value.zig:5:5
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

Это ошибка, так как программист пытался передать значение, известное только во время выполнения, функции, ожидающей значение, известное во время компиляции.

Другой способ получения ошибки — передача типа, нарушающего проверку типов при анализе функции. Это означает типизацию по умолчанию во время компиляции.

Например:

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);
}
Оболочка
$ zig test test_comptime_mismatched_type.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: doc/langref/test_comptime_mismatched_type.zig:5:12
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

С другой стороны, внутри определения функции с параметром comptime значение известно во время компиляции. Это означает, что мы фактически могли бы сделать это для типа bool, если бы захотели:

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);
}
Оболочка
$ 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);
}
Оболочка
$ 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();
    }
}
Оболочка
$ zig test test_comptime_call_extern_function.zig
doc/langref/test_comptime_call_extern_function.zig:5:13: error: comptime call of extern function
        exit();
        ~~~~^~

Нелогично, чтобы программа могла вызвать exit() (или любую другую внешнюю функцию) во время компиляции, поэтому это ошибка компиляции. Однако выражение comptime делает гораздо больше, чем иногда вызывает ошибку компиляции.

Внутри выражения comptime:

  • Все переменные являются переменными comptime.
  • Все выражения if, while, for и switch вычисляются во время компиляции или генерируют ошибку компиляции, если это невозможно.
  • Все выражения return и try недействительны (если функция сама не вызывается во время компиляции).
  • Весь код с побочными эффектами во время выполнения или зависящий от значений во время выполнения генерирует ошибку компиляции.
  • Все вызовы функций заставляют компилятор интерпретировать функцию во время компиляции, генерируя ошибку компиляции, если функция пытается сделать что-то, что имеет глобальные побочные эффекты во время выполнения.

Это означает, что программист может создать функцию, которая вызывается как во время компиляции, так и во время выполнения, без необходимости вносить какие-либо изменения в функцию.

Давайте рассмотрим пример:

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);
}
Командная строка
$ 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);
}
Командная строка
$ zig test test_fibonacci_comptime_overflow.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);
                     ~~~~~~^~~
doc/langref/test_fibonacci_comptime_overflow.zig:5:21: note: called from here (7 times)
    return fibonacci(index - 1) + fibonacci(index - 2);
           ~~~~~~~~~^~~~~~~~~~~
doc/langref/test_fibonacci_comptime_overflow.zig:9:34: note: called from here
    try comptime expect(fibonacci(7) == 13);
                        ~~~~~~~~~^~~

Компилятор генерирует ошибку, представляющую собой стек-трейс попытки вычисления функции во время компиляции.

К счастью, мы использовали целые числа без знака, и поэтому, когда мы попытались вычесть 1 из 0, это вызвало неопределённое поведение, что всегда является ошибкой компиляции, если компилятор знает, что это произошло. Но что бы произошло, если бы мы использовали целые числа со знаком?

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);
}
Командная строка
$ zig test test_fibonacci_comptime_unreachable.zig
lib/std/debug.zig:412:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^~~~~~~~~~~
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);
}
Командная строка
$ 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 использует возможности comptime для реализации общих структур данных без введения какого-либо специального синтаксиса.

Вот пример общей структуры данных 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 ссылается на себя как на указатель, у которого есть хорошо определённый размер во время компиляции, поэтому всё работает нормально.

Случайное исследование: print в Zig

Объединяя всё это, давайте посмотрим, как работает 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 });
}
Командная строка
$ 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 частично вычисляет функцию и генерирует функцию, которая фактически выглядит так:

Emitted print Function
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,
    });
}
Командная строка
$ zig test test_print_too_many_args.zig
lib/std/fmt.zig:203:18: error: unused argument in 'here is a string: '{s}' here is a number: {}
                               '
            1 => @compileError("unused argument in '" ++ fmt ++ "'"),
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
referenced by:
    print__anon_2377: lib/std/io/Writer.zig:24:26
    print: lib/std/io.zig:324:47
    remaining reference traces hidden; use '-freference-trace' to see all reference traces

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 });
}
Командная строка
$ 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 до самого конца.

См. также:

  • inline while
  • inline for

Ассемблер

В некоторых случаях может потребоваться прямой контроль над машинным кодом, генерируемым программами Zig, вместо того, чтобы полагаться на генерацию кода Zig. В таких случаях можно использовать встроенный ассемблер. Вот пример реализации "Hello, world" на x86_64 Linux с помощью встроенного ассемблера:

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"
    );
}
Командная строка
$ 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 является ошибочной и не хорошо протестированной.

Возможно, в будущем Zig будет иметь собственный ассемблер. Это позволило бы ему более тесно интегрироваться в язык, а также быть совместимым с популярным синтаксисом NASM. Этот раздел документации будет обновлён до выхода версии 1.0.0 с окончательным заявлением о статусе синтаксиса AT&T по сравнению с Intel/NASM.

Ограничения вывода

Ограничения вывода всё ещё считаются нестабильными в Zig, поэтому для понимания семантики необходимо использовать документацию LLVM и GCC .

Обратите внимание, что некоторые изменения в ограничениях вывода планируются в рамках задачи #215.

Ограничения ввода

Ограничения ввода всё ещё считаются нестабильными в Zig, поэтому для понимания семантики необходимо использовать документацию LLVM и GCC .

Обратите внимание, что некоторые изменения в ограничениях ввода планируются в рамках задачи #215.

Clobbers

Clobbers — это набор регистров, значения которых не сохраняются после выполнения ассемблерного кода. Они не включают выходные или входные регистры. Специальное значение clobber "memory" означает, что ассемблер вызывает запись в произвольные не объявленные места памяти — не только в память, на которую указывает объявленный косвенный выход.

Отсутствие объявления полного набора clobbers для данного выражения встроенного ассемблера не проверяется неопределённое поведение.

Глобальный ассемблер

Когда выражение ассемблера встречается на уровне контейнера в блоке comptime, это глобальный ассемблер.

Такой ассемблер имеет разные правила, чем встроенный ассемблер. Во-первых, volatile недействителен, поскольку весь глобальный ассемблер включён безусловно. Во-вторых, нет входных, выходных или clobbers. Весь глобальный ассемблер конкатенируется в одну длинную строку и собирается вместе. Нет правил подстановки шаблонов по отношению к %, как есть во встроенных ассемблерных выражениях.

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);
}
Командная строка
$ zig test test_global_assembly.zig -target x86_64-linux
1/1 test_global_assembly.test.global assembly...OK
All 1 tests passed.

Атомарные операции

TODO: @fence()

TODO: @atomic rmw

TODO: встроенный перечисление типов атомарного порядка памяти

См. также:

  • @atomicLoad
  • @atomicStore
  • @atomicRmw
  • @fence
  • @cmpxchgWeak
  • @cmpxchgStrong

Функции Async

Функции Async были регрессированы с выпуском 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.

См. также:

  • @atomicStore
  • @atomicRmw
  • @fence
  • @cmpxchgWeak
  • @cmpxchgStrong

@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
  • @atomicLoad
  • @fence
  • @cmpxchgWeak
  • @cmpxchgStrong

@atomicStore

@atomicStore(comptime T: type, ptr: *T, value: T, comptime ordering: AtomicOrder) void

Эта встроенная функция обращается к указателю на T и атомарно сохраняет заданное значение.

T должен быть указателем, bool, числом с плавающей точкой, целым числом или перечислением.

AtomicOrder можно найти с помощью @import("std").builtin.AtomicOrder.

См. также:

  • @atomicLoad
  • @atomicRmw
  • @fence
  • @cmpxchgWeak
  • @cmpxchgStrong

@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

Возвращает битовый сдвиг поля относительно содержащей его структуры.

Для непростых структур это всегда будет кратно 8. Для упакованных структур поля с небайтовым выравниванием будут иметь один байтовый сдвиг, но разные битовые сдвиги.

См. также:

  • @offsetOf

@bitSizeOf

@bitSizeOf(comptime T: type) comptime_int

Эта функция возвращает количество битов, необходимых для хранения T в памяти, если тип был бы полем в упакованной структуре/союзе. Результат — константа, специфичная для целевой платформы.

Эта функция измеряет размер во время выполнения. Для типов, которые недопустимы во время выполнения, таких как comptime_int и type, результат — 0.

См. также:

  • @sizeOf
  • @typeInfo

@breakpoint

@breakpoint() void

Эта функция вставляет платформоспецифичную инструкцию отладки, которая заставляет отладчики остановиться на ней. В отличие от @trap(), выполнение может продолжиться после этой точки, если программа возобновляется.

Эта функция допустима только в пределах области функции.

См. также:

  • @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 может быть целым числом или вектором.

Меняет порядок байтов целого числа. Это преобразует целое число с big endian в little endian и наоборот.

Обратите внимание, что для целей размещения памяти с точки зрения порядка байтов тип целого числа должен быть связан с количеством байтов, указанных функцией @sizeOf. Это демонстрируется с помощью u24. @sizeOf(u24) == 4, что означает, что целое число u24 в памяти занимает 4 байта, и эти 4 байта меняют свой порядок в системах little и big endian. С другой стороны, если 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

Возвращает байтовый сдвиг поля относительно содержащей его структуры.

См. также:

  • @bitOffsetOf

@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;
}
Командная строка
$ 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 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", {})

См. также:

  • Импорт из заголовка файла C
  • @cInclude
  • @cImport
  • @cUndef
  • void

@cImport

@cImport(expression) type

Эта функция анализирует код C и импортирует функции, типы, переменные и совместимые определения макросов в новый пустой тип структуры, а затем возвращает этот тип.

expression интерпретируется на этапе компиляции. Встроенные функции @cInclude, @cDefine, и @cUndef работают в этом выражении, добавляя в временной буфер, который затем анализируется как код C.

Обычно в вашем приложении должен быть только один @cImport, потому что он позволяет компилятору избежать многократного вызова clang и предотвращает дублирование встроенных функций.

Причинами наличия нескольких @cImport выражений могут быть:

  • Избегание коллизий символов, например, если foo.h и bar.h оба #define CONNECTION_COUNT
  • Анализ кода C с различными препроцессорными определениями

См. также:

  • Импорт из заголовка файла C
  • @cInclude
  • @cDefine
  • @cUndef

@cInclude

@cInclude(comptime path: []const u8) void

Эта функция может использоваться только внутри @cImport.

Она добавляет #include <$path>\n в временный буфер c_import.

См. также:

  • Импорт из заголовочного файла C
  • @cImport
  • @cDefine
  • @cUndef

@clz

@clz(operand: anytype) anytype

@TypeOf(operand) должен быть целочисленного типа или целочисленного векторного типа.

operand может быть целым числом или вектором.

Подсчитывает количество старших (ведущих в формате big-endian) нулей в целом числе — "подсчёт старших нулей".

Если operand является известным на этапе компиляции целым числом, возвращаемый тип — comptime_int. В противном случае, возвращаемый тип — беззнаковое целое число или вектор беззнаковых целых чисел с минимальным количеством битов, достаточным для представления количества нулей в целом числе указанного типа.

Если operand равно нулю, @clz возвращает разрядность целочисленного типа T.

См. также:

  • @ctz
  • @popCount

@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

См. также:

  • @atomicStore
  • @atomicLoad
  • @atomicRmw
  • @fence
  • @cmpxchgWeak

@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

См. также:

  • @atomicStore
  • @atomicLoad
  • @atomicRmw
  • @fence
  • @cmpxchgStrong

@compileError

@compileError(comptime msg: []const u8) noreturn

Эта функция, при семантическом анализе, вызывает ошибку компиляции с сообщением msg.

Существуют способы, позволяющие избежать семантической проверки кода, такие как использование if или switch с константами времени компиляции, а также comptime функции.

@compileLog

@compileLog(args: ...) void

Эта функция выводит переданные ей аргументы во время компиляции.

Чтобы предотвратить случайное оставление сообщений compile log в коде, в сборке добавляется ошибка компиляции, указывающая на заявление compile log. Эта ошибка препятствует генерации кода, но не мешает анализу.

Эта функция может использоваться для отладки кода, выполняющегося на этапе компиляции, используя "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});
}
Командная строка
$ zig test test_compileLog_builtin.zig
doc/langref/test_compileLog_builtin.zig:11:5: error: found compile log statement
    @compileLog("comptime in main");
    ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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 может быть целым числом или вектором.

Подсчитывает количество младших (конечных в формате big-endian) нулей в целом числе — "подсчёт конечных нулей".

Если operand является известным на этапе компиляции целым числом, возвращаемый тип — comptime_int. В противном случае, возвращаемый тип — беззнаковое целое число или вектор беззнаковых целых чисел с минимальным количеством битов, достаточным для представления количества нулей в целом числе указанного типа.

Если operand равно нулю, @ctz возвращает разрядность целочисленного типа T.

См. также:

  • @clz
  • @popCount

@cUndef

@cUndef(comptime name: []const u8) void

Эта функция может использоваться только внутри @cImport.

Она добавляет #undef $name в временный буфер @cImport.

См. также:

  • Импорт из заголовочного файла C
  • @cImport
  • @cDefine
  • @cInclude

@cVaArg

@cVaArg(operand: *std.builtin.VaList, comptime T: type) T

Реализует C макрос va_arg.

См. также:

  • @cVaCopy
  • @cVaEnd
  • @cVaStart

@cVaCopy

@cVaCopy(src: *std.builtin.VaList) std.builtin.VaList

Реализует C макрос va_copy.

См. также:

  • @cVaArg
  • @cVaEnd
  • @cVaStart

@cVaEnd

@cVaEnd(src: *std.builtin.VaList) void

Реализует C макрос va_end.

См. также:

  • @cVaArg
  • @cVaCopy
  • @cVaStart

@cVaStart

@cVaStart() std.builtin.VaList

Реализует C макрос va_start. Действителен только внутри вариабельной функции.

См. также:

  • @cVaArg
  • @cVaCopy
  • @cVaEnd

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

См. также:

  • @divTrunc
  • @divFloor

@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
  • @divExact

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

См. также:

  • @divFloor
  • @divExact

@embedFile

@embedFile(comptime path: []const u8) *const [N:0]u8

Эта функция возвращает константный указатель времени компиляции на нуль-терминированный массив фиксированного размера длиной, равной количеству байтов файла, заданного path. Содержимое массива — содержимое файла. Эквивалентно строковой литерали с содержимым файла.

path может быть абсолютным или относительным по отношению к текущему файлу, так же, как @import.

См. также:

  • @import

@enumFromInt

@enumFromInt(integer: anytype) anytype

Преобразует целое число в значение перечисления (enum). Возвращаемый тип — выведенный тип результата.

Попытка преобразовать целое число, не представляющее значение в выбранном типе перечисления, вызывает неопределенное поведение с проверкой безопасности.

См. также:

  • @intFromEnum

@errorFromInt

@errorFromInt(value: std.meta.Int(.unsigned, @bitSizeOf(anyerror))) anyerror

Преобразует целочисленное представление ошибки в тип глобального набора ошибок.

В общем случае рекомендуется избегать такого преобразования, так как целочисленное представление ошибки не стабильно относительно изменений исходного кода.

Попытка преобразовать целое число, не соответствующее ни одной ошибке, приводит к защищённому от ошибок неопределённому поведению.

См. также:

  • @intFromError

@errorName

@errorName(err: anyerror) [:0]const u8

Эта функция возвращает строковое представление ошибки. Строковое представление error.OutOfMem — "OutOfMem".

Если в приложении нет вызовов @errorName, или все вызовы имеют известное на этапе компиляции значение для err, таблица имён ошибок не будет сгенерирована.

@errorReturnTrace

@errorReturnTrace() ?*builtin.StackTrace

Если бинарник скомпилирован с отслеживанием возвращаемых ошибок, и эта функция вызвана в функции, которая вызывает функцию с возвращаемым типом ошибки или объединением ошибок, возвращает объект стека вызовов. В противном случае возвращает null.

@errorCast

@errorCast(value: anytype) anytype

Преобразует значение набора ошибок или объединения ошибок из одного набора ошибок в другой набор ошибок. Возвращаемый тип — выведенный тип результата. Попытка преобразовать ошибку, которая не находится в целевом наборе ошибок, приводит к защищённому от ошибок неопределённому поведению.

@export

@export(declaration, comptime options: std.builtin.ExportOptions) void

Создаёт символ в объектном файле вывода.

declaration должен быть одним из двух:

  • Идентификатор (x) для обозначения функции или переменной.
  • Доступ к полю (x.y) для поиска функции или переменной.

Этот встроенный элемент можно вызвать из блока comptime, чтобы условно экспортировать символы. Когда declaration — это функция с соглашением вызова C, а options.linkage — Strong, это эквивалентно ключевому слову export для функции:

export_builtin.zig
comptime {
    @export(internalName, .{ .name = "foo", .linkage = .strong });
}

fn internalName() callconv(.C) void {}
Оболочка
$ zig build-obj export_builtin.zig

Это эквивалентно:

export_builtin_equivalent_code.zig
export fn foo() void {}
Оболочка
$ 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 {}
Оболочка
$ zig build-obj export_any_symbol_name.zig

При просмотре результирующего объекта вы увидите, что символ используется дословно:

00000000000001f0 T A function name that is a complete sentence.

См. также:

  • Экспорт библиотеки C

@extern

@extern(T: type, comptime options: std.builtin.ExternOptions) T

Создает ссылку на внешний символ в выходном объектовом файле. T должен быть типом указателя.

См. также:

  • @export

@fence

@fence(order: AtomicOrder) void

Функция fence используется для введения ребер «происходит до» между операциями.

AtomicOrder можно найти с помощью @import("std").builtin.AtomicOrder.

См. также:

  • @atomicStore
  • @atomicLoad
  • @atomicRmw
  • @cmpxchgWeak
  • @cmpxchgStrong

@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);
}
Оболочка
$ 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

Принимая указатель на поле, возвращает базовый указатель структуры.

@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"));
}
Оболочка
$ zig test test_hasDecl_builtin.zig
1/1 test_hasDecl_builtin.test.@hasDecl...OK
All 1 tests passed.

См. также:

  • @hasField

@hasField

@hasField(comptime Container: type, comptime name: []const u8) bool

Возвращает, существует ли имя поля структуры, объединения или перечисления.

Результат — константа времени компиляции.

Он не включает функции, переменные или константы.

См. также:

  • @hasDecl

@import

@import(comptime path: []const u8) type

Эта функция находит файл zig, соответствующий path, и добавляет его в сборку, если он еще не добавлен.

Файлы исходного кода Zig неявно являются структурами с именем, равным имени файла без расширения. @import возвращает тип структуры, соответствующий файлу.

Объявления, имеющие ключевое слово pub, могут быть использованы в другом файле исходного кода, отличном от того, в котором они объявлены.

path может быть относительным путем или именем пакета. Если это относительный путь, он относительный к файлу, содержащему вызов функции @import.

Следующие пакеты всегда доступны:

  • @import("std") - Стандартная библиотека Zig
  • @import("builtin") - Информация, специфичная для целевой платформы. Команда zig build-exe --show-builtin выводит исходный код в стандартный вывод для справки.
  • @import("root") - Файл исходного кода корня. Обычно это src/main.zig, но зависит от того, какой файл компилируется.

См. также:

  • Переменные компиляции
  • @embedFile

@inComptime

@inComptime() bool

Возвращает, был ли встроенный элемент запущен в контексте comptime. Результат — константа времени компиляции.

Это можно использовать для предоставления альтернативных, дружественных к компиляции реализаций функций. Его не следует использовать, например, для исключения определенных функций из оценки в процессе компиляции.

См. также:

  • comptime

@intCast

@intCast(int: anytype) anytype

Преобразует целое число в другое целое число, сохраняя при этом то же числовое значение. Тип возвращаемого значения — это выведенный тип результата. Попытка преобразовать число, которое выходит за пределы диапазона целевого типа, приводит к безопасному поведению, которое не определено Undefined Behavior.

test_intCast_builtin.zig
test "integer cast panic" {
    var a: u16 = 0xabcd; // runtime-known
    _ = &a;
    const b: u8 = @intCast(a);
    _ = b;
}
Оболочка
$ zig test test_intCast_builtin.zig
1/1 test_intCast_builtin.test.integer cast panic...thread 3573820 panic: integer cast truncated bits
/home/andy/src/zig/doc/langref/test_intCast_builtin.zig:4:19: 0x103ce6b in test.integer cast panic (test)
    const b: u8 = @intCast(a);
                  ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x1048290 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103e24b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d389 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103cef1 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/a3b6539437d55800e891e62090700351/test

Для усечения значащих битов числа, выходящего за пределы диапазона целевого типа, используйте @truncate.

Если T равно comptime_int, это семантически эквивалентно преобразованию типов.

@intFromBool

@intFromBool(value: bool) u1

Преобразует true в @as(u1, 1) и false в @as(u1, 0).

@intFromEnum

@intFromEnum(enum_or_tagged_union: anytype) anytype

Преобразует значение перечисления в его целочисленный тип тега. Если передается помеченная объединение, используется значение тега в качестве значения перечисления.

Если существует только одно возможное значение перечисления, результат — comptime_int известное в момент компиляции.

См. также:

  • @enumFromInt

@intFromError

@intFromError(err: anytype) std.meta.Int(.unsigned, @bitSizeOf(anyerror))

Поддерживает следующие типы:

  • Глобальный набор ошибок
  • Тип набора ошибок
  • Тип объединения ошибок

Преобразует ошибку в целочисленное представление ошибки.

В целом рекомендуется избегать этого преобразования, так как целочисленное представление ошибки не стабильно при изменениях исходного кода.

См. также:

  • @errorFromInt

@intFromFloat

@intFromFloat(float: anytype) anytype

Преобразует целую часть числа с плавающей запятой в выведенный тип результата.

Если целая часть числа с плавающей запятой не может поместиться в целевом типе, происходит безопасная проверка, связанная с неопределенным поведением.

См. также:

  • @floatFromInt

@intFromPtr

@intFromPtr(value: anytype) usize

Преобразует value в usize, который представляет собой адрес указателя. value может быть *T или ?*T.

Для преобразования в обратном направлении используйте @ptrFromInt

@max

@max(a: T, b: T) T

Возвращает максимальное значение a и b. Этот встроенный элемент принимает целые числа, числа с плавающей запятой и векторы любого из них. В последнем случае операция выполняется поэлементно.

NaN обрабатываются следующим образом: если один из операндов (парной) операции — NaN, возвращается другой операнд. Если оба операнда — NaN, возвращается NaN.

См. также:

  • @min
  • Вектора

@memcpy

@memcpy(noalias dest, noalias source) void

Эта функция копирует байты из одной области памяти в другую.

dest должно быть изменяемым слайсом, изменяемым указателем на массив или изменяемым указателем на множество элементов указателя. Он может иметь любую выравнивание и любой тип элементов.

source должно быть слайсом, указателем на массив или указателем на множество элементов указателя. Он может иметь любое выравнивание и любой тип элементов.

Тип элемента source должен поддерживать преобразование типов в тип элемента dest. Типы элементов могут иметь разный размер ABI, однако это может повлиять на производительность.

Подобно циклам for, по крайней мере один из source и dest должен предоставлять длину, и если предоставлены две длины, они должны быть равны.

И наконец, две области памяти не должны перекрываться.

@memset

@memset(dest, elem) void

Эта функция устанавливает все элементы области памяти в elem.

dest должно быть изменяемым слайсом или изменяемым указателем на массив. Он может иметь любое выравнивание и любой тип элементов.

elem преобразуется в тип элемента dest.

Для безопасного обнуления конфиденциальных содержимого из памяти используйте std.crypto.utils.secureZero

@min

@min(a: T, b: T) T

Возвращает минимальное значение a и b. Этот встроенный элемент принимает целые числа, числа с плавающей запятой и векторы любого из них. В последнем случае операция выполняется поэлементно.

NaN обрабатываются следующим образом: если один из операндов (парной) операции — NaN, возвращается другой операнд. Если оба операнда — NaN, возвращается NaN.

См. также:

  • @max
  • Вектора

@wasmMemorySize

@wasmMemorySize(index: u32) usize

Эта функция возвращает размер памяти Wasm, идентифицированной как index, в качестве беззнакового значения в единицах страниц Wasm. Обратите внимание, что каждая страница Wasm имеет размер 64 КБ.

Эта функция является низкоуровневым встроенным инструментом без механизмов безопасности, обычно полезным для разработчиков аллокаторов, ориентированных на Wasm. Поэтому, если вы не пишете новый аллокатор с нуля, используйте что-то вроде @import("std").heap.WasmPageAllocator.

См. также:

  • @wasmMemoryGrow

@wasmMemoryGrow

@wasmMemoryGrow(index: u32, delta: usize) isize

Эта функция увеличивает размер памяти Wasm, идентифицированной как index, на delta единиц беззнаковых страниц Wasm. Обратите внимание, что каждая страница Wasm имеет размер 64 КБ. В случае успеха возвращает предыдущий размер памяти; в случае неудачи, если выделение не удалось, возвращает -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));
}
Командная строка
$ zig test test_wasmMemoryGrow_builtin.zig
1/1 test_wasmMemoryGrow_builtin.test.@wasmMemoryGrow...SKIP
0 passed; 1 skipped; 0 failed.

См. также:

  • @wasmMemorySize

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

См. также:

  • @rem

@mulWithOverflow

@mulWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }

Выполняет a * b и возвращает кортеж с результатом и возможным флагом переполнения.

@panic

@panic(message: []const u8) noreturn

Вызывает обработчик ошибок. По умолчанию обработчик ошибок вызывает публичную функцию panic в файле исходного кода корневого модуля, или, если таковая не определена, функцию std.builtin.default_panic из std/builtin.zig.

В общем случае предпочтительнее использовать @import("std").debug.panic. Однако, @panic может быть полезен в двух сценариях:

  • Из библиотечного кода, вызывая пользовательскую функцию обработки ошибок, если она была определена в корневом файле исходного кода.
  • При смешанном использовании кода C и Zig, вызывая стандартную функцию обработки ошибок во всех файлах .o.

См. также:

  • Файл исходного кода корневого модуля

@popCount

@popCount(operand: anytype) anytype

@TypeOf(operand) должен быть целым типом.

operand может быть целым числом или вектором.

Подсчитывает количество установленных битов в целом числе — «количество единиц».

Если operand — известное на этапе компиляции целое число, тип результата — comptime_int. В противном случае, тип результата — беззнаковое целое число или вектор беззнаковых целых чисел с минимальным количеством битов, достаточным для представления количества единиц в битах типа целого числа.

См. также:

  • @ctz
  • @clz

@prefetch

@prefetch(ptr: anytype, comptime options: PrefetchOptions) void

Этот встроенный инструмент сообщает компилятору выдать инструкцию предвычисления, если она поддерживается целевым процессором. Если целевой процессор не поддерживает запрошенную инструкцию предвычисления, этот встроенный инструмент является бесполезной операцией. Эта функция не влияет на поведение программы, только на её характеристики производительности.

Аргумент ptr может быть любым типом указателя и определяет адрес памяти для предвычисления. Эта функция не обращается к значению указателя; вполне законно передавать указатель на недействительную память в эту функцию, и это не приведёт к некорректным действиям.

PrefetchOptions можно найти с помощью @import("std").builtin.PrefetchOptions.

@ptrCast

@ptrCast(value: anytype) anytype

Преобразует указатель одного типа в указатель другого типа. Тип возвращаемого значения — выведенный тип результата.

Разрешены указатели с возможностью быть нулевыми. Преобразование нулевого указателя с возможностью быть нулевым в не-нулевой указатель вызывает проверку на ошибки, а значит, неопределённое поведение.

@ptrCast нельзя использовать для:

  • Удаления квалификатора const, используйте @constCast.
  • Удаления квалификатора volatile, используйте @volatileCast.
  • Изменения адресного пространства указателя, используйте @addrSpaceCast.
  • Увеличения выравнивания указателя, используйте @alignCast.
  • Преобразования не-слайсового указателя в слайс, используйте синтаксис срезов ptr[start..end].

@ptrFromInt

@ptrFromInt(address: usize) anytype

Преобразует целое число в указатель. Тип возвращаемого значения — выведенный тип результата. Для преобразования в обратном направлении используйте @intFromPtr. Преобразование адреса 0 в целевой тип, который не является указателем с возможностью быть нулевым и не имеет атрибута allowzero, приведёт к ошибке Pointer Cast Invalid Null при включенных проверках безопасности во время выполнения.

Если целевой тип указателя не допускает ноль в качестве адреса, а 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.

См. также:

  • @mod

@returnAddress

@returnAddress() usize

Эта функция возвращает адрес следующей машинной инструкции, которая будет выполнена после возвращения из текущей функции.

Последствия этого зависят от целевой платформы и не являются консистентными на всех платформах.

Эта функция имеет смысл только в рамках области видимости функции. Если функция встраивается в вызывающую функцию, возвращаемый адрес будет относиться к вызывающей функции.

@select

@select(comptime T: type, pred: @Vector(len, bool), a: @Vector(len, T), b: @Vector(len, T)) @Vector(len, T)

Выбирает значения поэлементно из a или b на основе pred. Если pred[i] равно true, соответствующий элемент результата будет a[i], в противном случае b[i].

См. также:

  • Векторы

@setAlignStack

@setAlignStack(comptime alignment: u29) void

Обеспечивает, что функция будет иметь выравнивание стека как минимум alignment байт.

@setCold

@setCold(comptime is_cold: bool) void

Сообщает оптимизатору, что текущая функция вызывается редко (или часто). Эта функция имеет смысл только в рамках области видимости функции.

@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) {}
    }
}
Командная строка
$ zig test test_without_setEvalBranchQuota_builtin.zig
doc/langref/test_without_setEvalBranchQuota_builtin.zig:4:9: error: evaluation exceeded 1000 backwards branches
        while (i < 1001) : (i += 1) {}
        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
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) {}
    }
}
Командная строка
$ zig test test_setEvalBranchQuota_builtin.zig
1/1 test_setEvalBranchQuota_builtin.test.foo...OK
All 1 tests passed.

См. также:

  • comptime

@setFloatMode

@setFloatMode(comptime mode: FloatMode) void

Изменяет правила текущей области видимости относительно определения операций с плавающей точкой.

  • Strict (по умолчанию) — операции с плавающей точкой строго следуют стандарту IEEE.
  • Optimized — операции с плавающей точкой могут делать следующее:
    • Предполагать, что аргументы и результат не являются NaN. Оптимизации требуются для сохранения определённого поведения с NaN, но значение результата не определено.
    • Предполагать, что аргументы и результат не являются +/-Inf. Оптимизации требуются для сохранения определённого поведения с +/-Inf, но значение результата не определено.
    • Считать знак нулевого аргумента или результата несущественным.
    • Использовать обратную величину аргумента вместо деления.
    • Выполнять контракцию операций с плавающей точкой (например, объединение умножения и сложения в умножение-сложение).
    • Выполнять алгебраически эквивалентные преобразования, которые могут изменить результаты в плавающей точке (например, перегруппировку).
    Эквивалентно -ffast-math в GCC.

Режим работы с плавающей точкой наследуется дочерними областями видимости и может быть изменён в любой области видимости. Вы можете установить режим работы с плавающей точкой в области видимости структуры или модуля с помощью блока comptime.

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; // undefined 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; // undefined behavior in all build modes.
        }
    }
}
Командная строка
$ zig test test_setRuntimeSafety_builtin.zig -OReleaseFast
1/1 test_setRuntimeSafety_builtin.test.@setRuntimeSafety...thread 3579742 panic: integer overflow
/home/andy/src/zig/doc/langref/test_setRuntimeSafety_builtin.zig:11:11: 0x100ae54 in test.@setRuntimeSafety (test)
        x += 1;
          ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x100c9b0 in main (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x100af44 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x100ae71 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/7c74a2939d84ddd0f45519d7a9f1c0f7/test

Примечание: планируется заменить @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 всегда производит результат и не может привести к ошибке компиляции.

См. также:

  • @shrExact
  • @shlWithOverflow

@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 — это неопределённое поведение.

См. также:

  • @shlExact
  • @shrExact

@shrExact

@shrExact(value: T, shift_amt: Log2T) T

Выполняет операцию правого сдвига (>>). Вызывающая сторона гарантирует, что сдвиг не выведет ни один бит 1.

Тип shift_amt — это целое без знака с log2(@typeInfo(T).Int.bits) битами. Это связано с тем, что shift_amt >= @typeInfo(T).Int.bits — это неопределённое поведение.

См. также:

  • @shlExact
  • @shlWithOverflow

@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)

Создаёт новый вектор, выбирая элементы из a и b на основе mask.

Каждый элемент в mask выбирает элемент из a или b. Положительные числа выбирают из a, начиная с 0. Отрицательные значения выбирают из b, начиная с -1 и двигаясь вниз. Рекомендуется использовать оператор ~ для индексов из b, чтобы оба индекса могли начинаться с 0 (т.е. ~@as(i32, 0) является -1).

Для каждого элемента mask, если он или выбранное значение из a или b равно undefined, то результирующий элемент равен undefined.

a_len и b_len могут отличаться по длине. Индексы элементов, выходящие за пределы границ в mask, приводят к ошибкам компиляции.

Если a или b — это undefined, это эквивалентно вектору всех 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_int и type, результат — 0.

См. также:

  • @bitSizeOf
  • @typeInfo

@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 должен быть целым, булевым, вещественным или указателем.

См. также:

  • Вектора
  • @shuffle

@reduce

@reduce(comptime op: std.builtin.ReduceOp, value: anytype) E

Преобразует вектор в скалярное значение (типа E) путём последовательного горизонтального редуцирования его элементов с помощью указанного оператора op.

Не все операторы доступны для всех типов элементов вектора:

  • Все операторы доступны для целых векторов.
  • .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.

См. также:

  • Вектора
  • @setFloatMode

@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)

Экспоненциальная функция по основанию e для вещественного числа. Использует специализированную инструкцию аппаратного обеспечения, если она доступна.

Поддерживает вещественные числа и векторы вещественных чисел.

@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)

Округляет данное вещественное число до целого числа, от нуля. Использует специализированную инструкцию аппаратного обеспечения, если она доступна.

Поддерживает вещественные числа и векторы вещественных чисел.

@subWithOverflow

@subWithOverflow(a: anytype, b: anytype) struct { @TypeOf(a, b), u1 }

Выполняет a - b и возвращает кортеж с результатом и возможным битом переполнения.

@tagName

@tagName(value: anytype) [:0]const u8

Преобразует значение перечисления или значения объединения в строковую литерал, представляющую имя.

Если перечисление не исчерпывающее, и значение тега не соответствует имени, оно вызывает проверку ошибок на неопределённое поведение Undefined Behavior.

@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(), выполнение не продолжается после этого момента.

Внешне по отношению к области действия функции этот встроенный элемент вызывает ошибку компиляции.

См. также:

  • @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);
}
Оболочка
$ 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.

Она доступна для следующих типов:

  • type
  • noreturn
  • void
  • bool
  • Целые числа — максимальное количество битов для целочисленного типа — 65535.
  • Вещественные числа
  • Указатели
  • comptime_int
  • comptime_float
  • @TypeOf(undefined)
  • @TypeOf(null)
  • Массивы
  • Необязательные значения
  • Тип набора ошибок
  • Тип объединения ошибок
  • Векторы
  • opaque
  • anyframe
  • структура
  • перечисление
  • Литералы перечисления
  • объединение
  • Функции

@typeInfo

@typeInfo(comptime T: type) std.builtin.Type

Предоставляет рефлексию типов.

Информация о типе структур, объединений, перечислений и наборов ошибок имеет поля, которые гарантированно расположены в том же порядке, что и в исходном файле.

Информация о типе структур, объединений, перечислений и opaques имеет объявления, которые также гарантированно расположены в том же порядке, что и в исходном файле.

@typeName

@typeName(T: type) *const [N:0]u8

Эта функция возвращает строковое представление типа в виде массива. Она эквивалентна строковой литерале имени типа. Возвращённое имя типа полностью квалифицировано: в имя типа включено родительское пространство имён с использованием точки.

@TypeOf

@TypeOf(...) type

@TypeOf — это специальная встроенная функция, которая принимает любое количество выражений в качестве параметров и возвращает тип результата, используя Резолюцию типа Peers.

Выражения вычисляются, но гарантируется, что они не имеют побочных эффектов во время выполнения:

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.*;
}
Оболочка
$ 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

Это то же самое, что и синтаксис инициализации объединений, за исключением того, что имя поля — это значение, известное во время компиляции (comptime), а не идентификатор.

@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
Оптимизации размера включены, безопасность выключена

Отладка

Оболочка
$ zig build-exe example.zig
  • Быстрая скорость компиляции
  • Включены проверки безопасности
  • Низкая производительность во время выполнения
  • Большой размер бинарного файла
  • Нет требований к воспроизводимости сборки

Быстрая сборка

Оболочка
$ zig build-exe example.zig -O ReleaseFast
  • Высокая производительность во время выполнения
  • Проверки безопасности отключены
  • Низкая скорость компиляции
  • Большой размер бинарного файла
  • Воспроизводимость сборки

Безопасная сборка

Оболочка
$ zig build-exe example.zig -O ReleaseSafe
  • Средняя производительность во время выполнения
  • Проверки безопасности включены
  • Низкая скорость компиляции
  • Большой размер бинарного файла
  • Воспроизводимость сборки

Компактная сборка

Оболочка
$ zig build-exe example.zig -O ReleaseSmall
  • Средняя производительность во время выполнения
  • Проверки безопасности отключены
  • Низкая скорость компиляции
  • Маленький размер бинарного файла
  • Воспроизводимость сборки

См. также:

  • Переменные компиляции
  • Система сборки Zig
  • Неопределённое поведение

Сборки с одним потоком

Zig имеет опцию компиляции -fsingle-threaded, которая имеет следующие последствия:

  • Все Локальные переменные потоков обрабатываются как обычные Переменные уровня контейнера.
  • Накладные расходы Асинхронных функций становятся эквивалентны накладным расходам вызова функции.
  • @import("builtin").single_threaded становится true, и, следовательно, различные API пользовательского уровня, которые считывают эту переменную, становятся более эффективными. Например, std.Mutex становится пустой структурой данных, а все его функции становятся операциями бездействия.

Неопределённое поведение

Zig имеет много случаев неопределённого поведения. Если неопределённое поведение обнаружено во время компиляции, Zig выдает ошибку компиляции и отказывается продолжать. Большинство случаев неопределённого поведения, которые нельзя обнаружить во время компиляции, можно обнаружить во время выполнения. В этих случаях Zig имеет проверки безопасности. Проверки безопасности можно отключить на основе блоков с помощью @setRuntimeSafety. Режимы сборки ReleaseFast и ReleaseSmall отключают все проверки безопасности (за исключением случаев, когда это переопределено с помощью @setRuntimeSafety) для оптимизации.

Когда проверка безопасности терпит неудачу, Zig завершается с трассировкой стека, подобной этой:

test_undefined_behavior.zig
test "safety check" {
    unreachable;
}
Оболочка
$ zig test test_undefined_behavior.zig
1/1 test_undefined_behavior.test.safety check...thread 3571226 panic: reached unreachable code
/home/andy/src/zig/doc/langref/test_undefined_behavior.zig:2:5: 0x103ce30 in test.safety check (test)
    unreachable;
    ^
/home/andy/src/zig/lib/compiler/test_runner.zig:157:25: 0x1048260 in mainTerminal (test)
        if (test_fn.func()) |_| {
                        ^
/home/andy/src/zig/lib/compiler/test_runner.zig:37:28: 0x103e21b in main (test)
        return mainTerminal();
                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103d359 in posixCallMainAndExit (test)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103cec1 in _start (test)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
error: the following test command crashed:
/home/andy/src/zig/.zig-cache/o/a71f9f37afd5b3c603d2b9ea72373fa7/test

Достижение недостижимого кода

Во время компиляции:

test_comptime_reaching_unreachable.zig
comptime {
    assert(false);
}
fn assert(ok: bool) void {
    if (!ok) unreachable; // assertion failure
}
Оболочка
$ zig test test_comptime_reaching_unreachable.zig
doc/langref/test_comptime_reaching_unreachable.zig:5:14: error: reached unreachable code
    if (!ok) unreachable; // assertion failure
             ^~~~~~~~~~~
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);
}
Оболочка
$ zig build-exe runtime_reaching_unreachable.zig
$ ./runtime_reaching_unreachable
thread 3575642 panic: reached unreachable code
/home/andy/src/zig/lib/std/debug.zig:412:14: 0x1036cdd in assert (runtime_reaching_unreachable)
    if (!ok) unreachable; // assertion failure
             ^
/home/andy/src/zig/doc/langref/runtime_reaching_unreachable.zig:4:21: 0x103507a in main (runtime_reaching_unreachable)
    std.debug.assert(false);
                    ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034929 in posixCallMainAndExit (runtime_reaching_unreachable)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034491 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;
}
Оболочка
$ zig test test_comptime_index_out_of_bounds.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];
}
Оболочка
$ zig build-exe runtime_index_out_of_bounds.zig
$ ./runtime_index_out_of_bounds
thread 3567952 panic: index out of bounds: index 5, len 5
/home/andy/src/zig/doc/langref/runtime_index_out_of_bounds.zig:7:13: 0x1037169 in foo (runtime_index_out_of_bounds)
    return x[5];
            ^
/home/andy/src/zig/doc/langref/runtime_index_out_of_bounds.zig:2:18: 0x10350b6 in main (runtime_index_out_of_bounds)
    const x = foo("hello");
                 ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034959 in posixCallMainAndExit (runtime_index_out_of_bounds)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10344c1 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;
}
Оболочка
$ zig test test_comptime_invalid_cast.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});
}
Оболочка
$ zig build-exe runtime_invalid_cast.zig
$ ./runtime_invalid_cast
thread 3567914 panic: attempt to cast negative value to unsigned integer
/home/andy/src/zig/doc/langref/runtime_invalid_cast.zig:6:27: 0x10351e2 in main (runtime_invalid_cast)
    const unsigned: u32 = @intCast(value);
                          ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034a49 in posixCallMainAndExit (runtime_invalid_cast)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10345b1 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;
}
Оболочка
$ zig test test_comptime_invalid_cast_truncate.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});
}
Оболочка
$ zig build-exe runtime_invalid_cast_truncate.zig
$ ./runtime_invalid_cast_truncate
thread 3576020 panic: integer cast truncated bits
/home/andy/src/zig/doc/langref/runtime_invalid_cast_truncate.zig:6:22: 0x1035275 in main (runtime_invalid_cast_truncate)
    const byte: u8 = @intCast(spartan_count);
                     ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034ad9 in posixCallMainAndExit (runtime_invalid_cast_truncate)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034641 in _start (runtime_invalid_cast_truncate)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Для обрезки битов используйте @truncate.

Переполнение целых чисел

Операции по умолчанию

Следующие операторы могут привести к переполнению целых чисел:

  • + (сложение)
  • - (вычитание)
  • - (отрицание)
  • * (умножение)
  • / (деление)
  • @divTrunc (деление)
  • @divFloor (деление)
  • @divExact (деление)

Пример с добавлением во время компиляции:

test_comptime_overflow.zig
comptime {
    var byte: u8 = 255;
    byte += 1;
}
Оболочка
$ zig test test_comptime_overflow.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});
}
Оболочка
$ zig build-exe runtime_overflow.zig
$ ./runtime_overflow
thread 3574209 panic: integer overflow
/home/andy/src/zig/doc/langref/runtime_overflow.zig:5:10: 0x103525e in main (runtime_overflow)
    byte += 1;
         ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034ad9 in posixCallMainAndExit (runtime_overflow)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034641 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});
}
Оболочка
$ zig build-exe math_add.zig
$ ./math_add
unable to add one: Overflow
error: Overflow
/home/andy/src/zig/lib/std/math.zig:565:21: 0x10352a5 in add__anon_2592 (math_add)
    if (ov[1] != 0) return error.Overflow;
                    ^
/home/andy/src/zig/doc/langref/math_add.zig:8:9: 0x1035243 in main (math_add)
        return err;
        ^

Встроенные функции переполнения

Эти встроенные функции возвращают кортеж, содержащий информацию о переполнении (как u1) и, возможно, переполненные биты операции:

  • @addWithOverflow
  • @subWithOverflow
  • @mulWithOverflow
  • @shlWithOverflow

Пример @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]});
    }
}
Оболочка
$ 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));
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_comptime_shlExact_overwlow.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});
}
Оболочка
$ zig build-exe runtime_shlExact_overflow.zig
$ ./runtime_shlExact_overflow
thread 3577156 panic: left shift overflowed bits
/home/andy/src/zig/doc/langref/runtime_shlExact_overflow.zig:6:5: 0x10352bd in main (runtime_shlExact_overflow)
    const y = @shlExact(x, 2);
    ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034b19 in posixCallMainAndExit (runtime_shlExact_overflow)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034681 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;
}
Оболочка
$ zig test test_comptime_shrExact_overflow.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});
}
Оболочка
$ zig build-exe runtime_shrExact_overflow.zig
$ ./runtime_shrExact_overflow
thread 3579049 panic: right shift overflowed bits
/home/andy/src/zig/doc/langref/runtime_shrExact_overflow.zig:6:5: 0x10352b9 in main (runtime_shrExact_overflow)
    const y = @shrExact(x, 2);
    ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034b19 in posixCallMainAndExit (runtime_shrExact_overflow)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034681 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;
}
Оболочка
$ zig test test_comptime_division_by_zero.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});
}
Оболочка
$ zig build-exe runtime_division_by_zero.zig
$ ./runtime_division_by_zero
thread 3575479 panic: division by zero
/home/andy/src/zig/doc/langref/runtime_division_by_zero.zig:7:17: 0x10351f6 in main (runtime_division_by_zero)
    const c = a / b;
                ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034a49 in posixCallMainAndExit (runtime_division_by_zero)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10345b1 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;
}
Оболочка
$ zig test test_comptime_remainder_division_by_zero.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});
}
Оболочка
$ zig build-exe runtime_remainder_division_by_zero.zig
$ ./runtime_remainder_division_by_zero
thread 3570535 panic: division by zero
/home/andy/src/zig/doc/langref/runtime_remainder_division_by_zero.zig:7:17: 0x10351f6 in main (runtime_remainder_division_by_zero)
    const c = a % b;
                ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034a49 in posixCallMainAndExit (runtime_remainder_division_by_zero)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10345b1 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;
}
Оболочка
$ zig test test_comptime_divExact_remainder.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});
}
Оболочка
$ zig build-exe runtime_divExact_remainder.zig
$ ./runtime_divExact_remainder
thread 3570103 panic: exact division produced remainder
/home/andy/src/zig/doc/langref/runtime_divExact_remainder.zig:7:15: 0x103526b in main (runtime_divExact_remainder)
    const c = @divExact(a, b);
              ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034a89 in posixCallMainAndExit (runtime_divExact_remainder)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10345f1 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;
}
Оболочка
$ zig test test_comptime_unwrap_null.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});
}
Оболочка
$ zig build-exe runtime_unwrap_null.zig
$ ./runtime_unwrap_null
thread 3570514 panic: attempt to use null value
/home/andy/src/zig/doc/langref/runtime_unwrap_null.zig:6:35: 0x1035272 in main (runtime_unwrap_null)
    const number = optional_number.?;
                                  ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034ad9 in posixCallMainAndExit (runtime_unwrap_null)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034641 in _start (runtime_unwrap_null)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Один из способов избежать этой ошибки заключается в проверке на Null вместо предположения о ненулевом значении с помощью выражения if:

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", .{});
    }
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_comptime_unwrap_error.zig
doc/langref/test_comptime_unwrap_error.zig:2:44: error: caught unexpected error 'UnableToReturnNumber'
    const number = getNumberOrFail() catch unreachable;
                                           ^~~~~~~~~~~
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;
}
Оболочка
$ zig build-exe runtime_unwrap_error.zig
$ ./runtime_unwrap_error
thread 3577877 panic: attempt to unwrap error: UnableToReturnNumber
/home/andy/src/zig/doc/langref/runtime_unwrap_error.zig:9:5: 0x103738f in getNumberOrFail (runtime_unwrap_error)
    return error.UnableToReturnNumber;
    ^
/home/andy/src/zig/doc/langref/runtime_unwrap_error.zig:4:44: 0x1035301 in main (runtime_unwrap_error)
    const number = getNumberOrFail() catch unreachable;
                                           ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034b49 in posixCallMainAndExit (runtime_unwrap_error)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10346b1 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;
}
Оболочка
$ 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;
}
Оболочка
$ zig test test_comptime_invalid_error_code.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});
}
Оболочка
$ zig build-exe runtime_invalid_error_code.zig
$ ./runtime_invalid_error_code
thread 3570441 panic: invalid error code
/home/andy/src/zig/doc/langref/runtime_invalid_error_code.zig:7:5: 0x10352a0 in main (runtime_invalid_error_code)
    const invalid_err = @errorFromInt(number);
    ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034ae9 in posixCallMainAndExit (runtime_invalid_error_code)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034651 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;
}
Оболочка
$ zig test test_comptime_invalid_enum_cast.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);
                   ^~~~~~~~~~~~~~~
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)});
}
Оболочка
$ zig build-exe runtime_invalid_enum_cast.zig
$ ./runtime_invalid_enum_cast
thread 3568027 panic: invalid enum value
/home/andy/src/zig/doc/langref/runtime_invalid_enum_cast.zig:12:20: 0x1035297 in main (runtime_invalid_enum_cast)
    const b: Foo = @enumFromInt(a);
                   ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034af9 in posixCallMainAndExit (runtime_invalid_enum_cast)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034661 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));
}
Оболочка
$ zig test test_comptime_invalid_error_set_cast.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});
}
Оболочка
$ zig build-exe runtime_invalid_error_set_cast.zig
$ ./runtime_invalid_error_set_cast
thread 3568026 panic: invalid error code
/home/andy/src/zig/doc/langref/runtime_invalid_error_set_cast.zig:15:21: 0x1037317 in foo (runtime_invalid_error_set_cast)
    const x: Set2 = @errorCast(set1);
                    ^
/home/andy/src/zig/doc/langref/runtime_invalid_error_set_cast.zig:12:8: 0x103523d in main (runtime_invalid_error_set_cast)
    foo(Set1.B);
       ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034ae9 in posixCallMainAndExit (runtime_invalid_error_set_cast)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034651 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;
}
Оболочка
$ zig test test_comptime_incorrect_pointer_alignment.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];
}
Оболочка
$ zig build-exe runtime_incorrect_pointer_alignment.zig
$ ./runtime_incorrect_pointer_alignment
thread 3570122 panic: incorrect alignment
/home/andy/src/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:9:64: 0x1034f0a in foo (runtime_incorrect_pointer_alignment)
    const int_slice = mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
                                                               ^
/home/andy/src/zig/doc/langref/runtime_incorrect_pointer_alignment.zig:5:12: 0x1034dc7 in main (runtime_incorrect_pointer_alignment)
    if (foo(bytes) != 0x11111111) return error.Wrong;
           ^
/home/andy/src/zig/lib/std/start.zig:524:37: 0x1034cc5 in posixCallMainAndExit (runtime_incorrect_pointer_alignment)
            const result = root.main() catch |err| {
                                    ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x10347e1 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,
};
Оболочка
$ zig test test_comptime_wrong_union_field_access.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;
    ~^~~~~~
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});
}
Оболочка
$ zig build-exe runtime_wrong_union_field_access.zig
$ ./runtime_wrong_union_field_access
thread 3578787 panic: access of union field 'float' while field 'int' is active
/home/andy/src/zig/doc/langref/runtime_wrong_union_field_access.zig:14:6: 0x103cd20 in bar (runtime_wrong_union_field_access)
    f.float = 12.34;
     ^
/home/andy/src/zig/doc/langref/runtime_wrong_union_field_access.zig:10:8: 0x103ac5c in main (runtime_wrong_union_field_access)
    bar(&f);
       ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x103a4f9 in posixCallMainAndExit (runtime_wrong_union_field_access)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x103a061 in _start (runtime_wrong_union_field_access)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Эта безопасность недоступна для extern или packed объединений.

Чтобы изменить активное поле объединения, присвойте все объединение целиком, как это показано ниже:

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});
}
Оболочка
$ 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;
}
Оболочка
$ zig build-exe undefined_active_union_field.zig
$ ./undefined_active_union_field
value: 1.234e1

См. также:

  • объединение
  • extern объединение

Преобразование числа с плавающей точкой в целое число за пределами границ

Это происходит при приведении float к integer, когда значение float выходит за пределы диапазона типа integer.

На этапе компиляции:

test_comptime_out_of_bounds_float_to_integer_cast.zig
comptime {
    const float: f32 = 4294967296;
    const int: i32 = @intFromFloat(float);
    _ = int;
}
Оболочка
$ zig test test_comptime_out_of_bounds_float_to_integer_cast.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;
}
Оболочка
$ zig build-exe runtime_out_of_bounds_float_to_integer_cast.zig
$ ./runtime_out_of_bounds_float_to_integer_cast
thread 3570320 panic: integer part of floating point value out of bounds
/home/andy/src/zig/doc/langref/runtime_out_of_bounds_float_to_integer_cast.zig:4:22: 0x1035169 in main (runtime_out_of_bounds_float_to_integer_cast)
    const int: i32 = @intFromFloat(float);
                     ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x10349a9 in posixCallMainAndExit (runtime_out_of_bounds_float_to_integer_cast)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034511 in _start (runtime_out_of_bounds_float_to_integer_cast)
    asm volatile (switch (native_arch) {
    ^
???:?:?: 0x0 in ??? (???)
(process terminated by signal)

Некорректное приведение указателя к значению Null

Это происходит при приведении указателя с адресом 0 к указателю, у которого адрес 0 может быть недопустимым. Например, указатели C-указатели, Необязательные указатели и указатели allowzero допускают адрес ноль, но обычные указатели — нет.

На этапе компиляции:

test_comptime_invalid_null_pointer_cast.zig
comptime {
    const opt_ptr: ?*i32 = null;
    const ptr: *i32 = @ptrCast(opt_ptr);
    _ = ptr;
}
Оболочка
$ zig test test_comptime_invalid_null_pointer_cast.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;
}
Оболочка
$ zig build-exe runtime_invalid_null_pointer_cast.zig
$ ./runtime_invalid_null_pointer_cast
thread 3572791 panic: cast causes pointer to be null
/home/andy/src/zig/doc/langref/runtime_invalid_null_pointer_cast.zig:4:23: 0x10350bc in main (runtime_invalid_null_pointer_cast)
    const ptr: *i32 = @ptrCast(opt_ptr);
                      ^
/home/andy/src/zig/lib/std/start.zig:514:22: 0x1034929 in posixCallMainAndExit (runtime_invalid_null_pointer_cast)
            root.main();
                     ^
/home/andy/src/zig/lib/std/start.zig:266:5: 0x1034491 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 имеет стандартный выделювач — malloc, realloc, и free. При подключении к 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;
}
Оболочка
$ zig test test_allocator.zig
1/1 test_allocator.test.using an allocator...OK
All 1 tests passed.

В приведенном выше примере для инициализации FixedBufferAllocator используется 100 байт памяти стека, которая затем передается функции. Для удобства доступен глобальный FixedBufferAllocator для быстрых тестов в std.testing.allocator, который также выполнит основную проверку утечек памяти.

Zig имеет универсальный выделювач, доступный для импорта с помощью std.heap.GeneralPurposeAllocator. Тем не менее, рекомендуется следовать руководству Выбор выделювача.

Выбор выделювача

Выбираемый выделювач зависит от ряда факторов. Вот блок-схема, которая поможет вам принять решение:

  1. Разрабатываете ли вы библиотеку? В этом случае лучше принять Allocator в качестве параметра и позволить пользователям вашей библиотеки решить, какой выделювач использовать.
  2. Подключаетесь ли вы к libc? В этом случае std.heap.c_allocator вероятно, является правильным выбором, по крайней мере, для вашего основного выделювача.
  3. Максимальное количество байтов, которое вам понадобится, ограничено числом, известным на этапе компиляции? В этом случае используйте std.heap.FixedBufferAllocator или std.heap.ThreadSafeFixedBufferAllocator, в зависимости от того, нужна ли вам потокобезопасность.
  4. Является ли ваша программа приложением командной строки, которое выполняется от начала до конца без каких-либо основных циклических шаблонов (таких как основной цикл видеоигры или обработчик запросов веб-сервера), так что имеет смысл освободить все сразу в конце? В этом случае рекомендуется следовать этому шаблону:
    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});
    }
    Оболочка
    $ zig build-exe cli_allocation.zig
    $ ./cli_allocation
    ptr=i32@7f989ee8e010
    
    При использовании такого выделювача нет необходимости вручную освобождать что-либо. Все освобождается сразу с вызовом arena.deinit().
  5. Относятся ли выделения к циклическому шаблону, такому как основной цикл видеоигры или обработчик запросов веб-сервера? Если все выделения можно освободить сразу в конце цикла, например, после того, как фрейм видеоигры был полностью отрисован или запрос веб-сервера был обработан, то std.heap.ArenaAllocator является отличным кандидатом. Как показано в предыдущем пункте, это позволяет вам освободить целые арены сразу. Обратите также внимание, что если можно установить верхнюю границу памяти, то std.heap.FixedBufferAllocator может быть использован как дополнительная оптимизация.
  6. Пишете ли вы тест, и хотите убедиться, что error.OutOfMemory обрабатывается правильно? В этом случае используйте std.testing.FailingAllocator.
  7. Пишете ли вы тест? В этом случае используйте std.testing.allocator.
  8. Наконец, если ни одно из вышеперечисленного не применимо, вам нужен универсальный выделювач. Универсальный выделювач Zig доступен в виде функции, которая принимает структуру компиляционного конфигурации и возвращает тип. Как правило, вы создадите один std.heap.GeneralPurposeAllocator в своей основной функции, а затем передадите его или подвыделювачи различным частям вашего приложения.
  9. Вы также можете рассмотреть Реализацию выделювача.

Где находятся байты?

Строковые литералы, такие как "hello", находятся в глобальной секции данных констант. Вот почему ошибка передавать строковый литерал в изменяемый срез, как в этом примере:

test_string_literal_to_slice.zig
fn foo(s: []u8) void {
    _ = s;
}

test "string literal to mutable slice" {
    foo("hello");
}
Оболочка
$ zig test test_string_literal_to_slice.zig
doc/langref/test_string_literal_to_slice.zig:6:9: error: expected type '[]u8', found '*const [5:0]u8'
    foo("hello");
        ^~~~~~~
doc/langref/test_string_literal_to_slice.zig:6:9: note: cast discards const qualifier
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");
}
Оболочка
$ 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, когда значение известно на этапе компиляции, хранятся в глобальной секции данных констант. Также переменные времени компиляции хранятся в глобальной секции данных констант.

Объявления var внутри функций хранятся в кадре стека функции. После возврата функции любые указатели на переменные в кадре стека функции становятся недействительными ссылками, и обращение к ним становится неконтролируемым неопределенным поведением.

Объявления var на верхнем уровне или в объявлениях структур хранятся в глобальной секции данных.

Местоположение памяти, выделенной с помощью allocator.alloc или allocator.create, определяется реализацией выделювача.

TODO: локальные переменные потока

Реализация выделювача

Программисты Zig могут реализовать свои собственные выделювачи, выполнив интерфейс Allocator. Для этого необходимо внимательно изучить документационные комментарии в std/mem.zig, а затем предоставить allocFn и resizeFn.

Существует много примеров выделювачей для вдохновения. Посмотрите на std/heap.zig и std.heap.GeneralPurposeAllocator.

Ошибка выделения памяти в куче

Многие языки программирования выбирают обработать возможность ошибки выделения памяти в куче, безусловно, завершая работу. По соглашению, программисты Zig не считают это удовлетворительным решением. Вместо этого error.OutOfMemory представляет ошибку выделения памяти в куче, и библиотеки Zig возвращают этот код ошибки всякий раз, когда ошибка выделения памяти в куче препятствует успешному выполнению операции.

Некоторые утверждают, что поскольку некоторые операционные системы, такие как Linux, по умолчанию имеют включенную возможность перераспределения памяти, нет смысла обрабатывать ошибку выделения памяти в куче. Здесь много проблем с этим рассуждением:

  • Только некоторые операционные системы имеют функцию перераспределения памяти.
    • В Linux она включена по умолчанию, но ее можно настроить.
    • В Windows перераспределения памяти нет.
    • Встраиваемые системы не имеют перераспределения памяти.
    • В любительских операционных системах перераспределение памяти может быть или отсутствовать.
  • Для систем реального времени не только отсутствует перераспределение памяти, но обычно максимальный объем памяти на приложение определяется заранее.
  • При написании библиотеки одной из основных целей является повторное использование кода. Правильная обработка ошибок выделения памяти делает библиотеку пригодной для повторного использования в более широком контексте.
  • Хотя некоторые программные продукты зависят от включения функции перераспределения памяти, ее существование является источником бесчисленных проблем с пользовательским опытом. Когда система с включенным перераспределением памяти, например, Linux по умолчанию, приближается к исчерпанию памяти, система зависает и становится неработоспособной. На этом этапе OOM Killer выбирает приложение для уничтожения на основе эвристических правил. Это непредсказуемое решение часто приводит к уничтожению важного процесса и часто не возвращает систему в рабочее состояние.

Рекурсия

Рекурсия — это фундаментальный инструмент при моделировании программного обеспечения. Однако у нее часто упускается из виду одна проблема: неограниченное выделение памяти.

Рекурсия — это область активных экспериментов в Zig, и поэтому приведенная здесь документация не окончательная. Вы можете прочитать краткое описание статуса рекурсии в примечаниях к выпуску 0.3.0.

Короткий вывод заключается в том, что в настоящее время рекурсия работает нормально, как ожидается. Хотя код Zig еще не защищен от переполнения стека, планируется, что будущая версия Zig предоставит такую защиту с некоторой степенью сотрудничества со стороны кода Zig.

Срок службы и владение

Программист Zig несет ответственность за обеспечение того, чтобы к указателю не был выполнен доступ, когда память, на которую он указывает, больше недоступна. Обратите внимание, что срез — это форма указателя, так как он ссылается на другую память.

Чтобы предотвратить ошибки, следует придерживаться некоторых полезных соглашений при работе с указателями. В общем случае, когда функция возвращает указатель, в документации к функции должно быть указано, кто «владеет» указателем. Эта концепция помогает программисту решить, когда и, если это необходимо, освободить указатель.

Например, документация функции может указать «вызывающая функция владеет возвращаемой памятью», в этом случае код, вызывающий функцию, должен иметь план, когда освободить эту память. Вероятно, в этой ситуации функция будет принимать параметр Allocator.

END_OF_DOCUMENT_MARKER

Иногда жизненный цикл указателя может быть более сложным. Например, срез 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.13.0";
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 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(&[_]std.Target.x86.Feature{
        .@"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_lzcnt,
        .fast_movbe,
        .fast_scalar_fsqrt,
        .fast_scalar_shift_masks,
        .fast_variable_perlane_shuffle,
        .fast_vector_fsqrt,
        .fma,
        .fsgsbase,
        .fsrm,
        .fxsr,
        .gfni,
        .invpcid,
        .lzcnt,
        .macrofusion,
        .mmx,
        .movbe,
        .mwaitx,
        .nopl,
        .pclmul,
        .pku,
        .popcnt,
        .prfchw,
        .rdpid,
        .rdpru,
        .rdrnd,
        .rdseed,
        .sahf,
        .sbb_dep_breaking,
        .sha,
        .shstk,
        .slow_shld,
        .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 = 9,
                .patch = 2,
            },
            .max = .{
                .major = 6,
                .minor = 9,
                .patch = 2,
            },
        },
        .glibc = .{
            .major = 2,
            .minor = 39,
            .patch = 0,
        },
    }},
};
pub const target: std.Target = .{
    .cpu = cpu,
    .os = os,
    .abi = abi,
    .ofmt = object_format,
    .dynamic_linker = std.Target.DynamicLinker.init("/nix/store/k7zgvzp2r31zkg9xqgjim7mbknryv6bs-glibc-2.39-52/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 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;

См. также:

  • Режим сборки

Файл исходного кода корня

TODO: Объяснить, как файл исходного кода корня находит другие файлы

TODO: pub fn main

TODO: pub fn panic

TODO: Если происходит связывание с libc, можно использовать export fn main

TODO: Порядок независимых объявлений верхнего уровня

TODO: Ленивый анализ

TODO: Использование comptime { _ = @import() }

Система сборки Zig

Система сборки Zig предоставляет кроссплатформенный и независимый от зависимостей способ объявления логики, необходимой для сборки проекта. С помощью этой системы логика сборки проекта записывается в файл build.zig, используя API системы сборки Zig для объявления и конфигурации артефактов сборки и других задач.

Некоторые примеры задач, которые может помочь решить система сборки:

  • Выполнение задач параллельно и кэширование результатов.
  • Зависимость от других проектов.
  • Предоставление пакета для зависимости от него других проектов.
  • Создание артефактов сборки путем выполнения компилятора Zig. Это включает в себя сборку исходного кода Zig, а также исходного кода C и C++.
  • Получение конфигурационных параметров пользователя и использование этих параметров для конфигурации сборки.
  • Представление конфигурации сборки в виде значений comptime путём предоставления файла, который можно импортировать кодом Zig.
  • Кэширование артефактов сборки, чтобы избежать излишнего повторения шагов.
  • Выполнение артефактов сборки или системных инструментов.
  • Запуск тестов и проверка соответствия выходных данных выполнения артефакта сборки ожидаемому значению.
  • Запуск zig fmt на кодовой базе или её подмножестве.
  • Пользовательские задачи.

Для использования системы сборки выполните zig build --help, чтобы увидеть справочное меню командной строки. Оно будет включать параметры, специфичные для проекта, объявленные в скрипте build.zig.

В настоящее время документация по системе сборки размещается на внешнем сайте: Документация по системе сборки

C

Хотя Zig независим от C и, в отличие от большинства других языков, не зависит от libc, Zig признаёт важность взаимодействия с существующим кодом C.

Существует несколько способов, которыми Zig облегчает взаимодействие с C.

Примитивные типы C

Эти типы гарантируют совместимость с ABI C и могут использоваться как и любой другой тип.

  • 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");
}
Командная строка
$ 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");
});

См. также:

  • @cImport
  • @cInclude
  • @cDefine
  • @cUndef
  • @import

CLI для трансляции C

Возможность трансляции C в Zig доступна как инструмент командной строки через zig translate-c. Он требует единственного имени файла в качестве аргумента. Он также может принять набор необязательных флагов, которые передаются в clang. Он записывает преобразованный файл в стандартный вывод.

Флаги командной строки

  • -I: Указывает каталог поиска файлов включаемых файлов. Может использоваться несколько раз. Эквивалентно флагу clang -I. Текущий каталог не включается по умолчанию; используйте -I., чтобы включить его.
  • -D: Определяет макрос препроцессора. Эквивалентно флагу clang -D.
  • -cflags [flags] --: Передает произвольные дополнительные флаги командной строки в clang. Обратите внимание: список флагов должен заканчиваться --
  • -target: тройное обозначение целевой архитектуры для преобразованного кода Zig. Если целевая архитектура не указана, будет использоваться текущая целевая архитектура.

Использование -target и -cflags

Важно! При трансляции C-кода с помощью zig translate-c вы обязательно должны использовать ту же -target архитектуру, которую вы будете использовать при компиляции преобразованного кода. Кроме того, вы обязательно должны убедиться, что используемые -cflags, если таковые имеются, соответствуют cflags, используемым на целевой системе. Использование неправильной -target или -cflags может привести к ошибкам парсинга clang или Zig, или к неявным проблемам совместимости ABI при компоновке с C-кодом.

varytarget.h
long FOO = __LONG_MAX__;
Командная строка
$ 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);
Командная строка
$ 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

@cImport и zig translate-c используют одинаковый базовый функционал трансляции C, поэтому с технической точки зрения они эквивалентны. На практике @cImport полезно как быстрый и простой способ доступа к числовым константам, определениям типов и записям без необходимости дополнительных настроек. Если вам нужно передать cflags в clang или вы хотите отредактировать преобразованный код, рекомендуется использовать zig translate-c и сохранить результаты в файл. Общие причины редактирования сгенерированного кода включают: изменение параметров anytype в макросах типа функций на более конкретные типы; изменение [*c]T указателей на [*]T или *T указатели для повышения безопасности типов; и включение или отключение проверки времени выполнения в определённых функциях.

См. также:

  • Архитектуры
  • Примитивные типы C
  • Указатели
  • Указатели C
  • Импорт из заголовочного файла C
  • @cInclude
  • @cImport
  • @setRuntimeSafety

Кэширование трансляции 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;
}
Командная строка
$ zig build-exe verbose_cimport_flag.zig -lc --verbose-cimport
info(compilation): C import source: /home/andy/src/zig/.zig-cache/o/f4e9c68cba40c97888f064d67b031021/cimport.h
info(compilation): C import .d file: /home/andy/src/zig/.zig-cache/o/f4e9c68cba40c97888f064d67b031021/cimport.h.d
info(compilation): C import output: /home/andy/src/zig/.zig-cache/o/1b63455e1d0d323f51bdc4909717e28b/cimport.zig
$ ./verbose_cimport_flag

cimport.h содержит файл для трансляции (составленный из вызовов @cInclude, @cDefine, и @cUndef), cimport.h.d — список зависимостей файла, а cimport.zig содержит преобразованный вывод.

См. также:

  • Импорт из заголовочного файла C
  • CLI для трансляции C
  • @cInclude
  • @cImport

Ошибки трансляции

Некоторые конструкции C не могут быть преобразованы в Zig — например, goto, структуры с битами и макросы с подстановкой токенов. Zig использует приведение к меньшему типу для продолжения трансляции в случае непереводимых сущностей.

Приведение к меньшему типу бывает трёх видов — непрозрачный, extern и @compileError. C-структуры и объединения, которые не могут быть правильно преобразованы, будут преобразованы как opaque{}. Функции, содержащие непрозрачные типы или конструкции кода, которые не могут быть преобразованы, будут применены к extern объявлениям. Таким образом, непереводимые типы по-прежнему могут использоваться как указатели, а непереводимые функции могут вызываться, если компоновщик знает о скомпилированной функции.

@compileError используется, когда определения верхнего уровня (глобальные переменные, прототипы функций, макросы) не могут быть преобразованы или приведены к меньшему типу. Поскольку Zig использует ленивый анализ для определений верхнего уровня, непереводимые сущности не приведут к ошибке компиляции в вашем коде, пока вы их не будете использовать.

См. также:

  • непрозрачный
  • extern
  • @compileError

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;
}
Оболочка
$ 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; это просто означает, что вы не можете напрямую использовать MAKELOCAL из Zig.

См. также:

  • @compileError

Указатели C

Этот тип следует избегать, когда это возможно. Единственная допустимая причина использования указателя C — это в автоматически сгенерированном коде из перевода кода C.

При импорте заголовочных файлов C неясно, следует ли переводить указатели как указатели на один элемент (*T) или на несколько элементов ([*]T). Указатели C являются компромиссом, чтобы код Zig мог напрямую использовать переведённые заголовочные файлы.

[*c]T — указатель C.

  • Поддерживает всю синтаксическую конструкцию других двух типов указателей (*T) и ([*]T).
  • Преобразуется в другие типы указателей, а также в Указатели с возможностью отсутствия. При преобразовании указателя C в не-указатель с возможностью отсутствия, проверка на безопасность Определённое поведение происходит, если адрес равен 0.
  • Разрешает адрес 0. На не-самостоятельных целях, обращение к адресу 0 проверяется на безопасность Определённое поведение. Указатели C с возможностью отсутствия добавляют ещё один бит для отслеживания значения NULL, как и ?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);
}
Оболочка
$ 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)));
}
Оболочка
$ 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;
}

Для создания статической библиотеки:

Оболочка
$ zig build-lib mathtest.zig

Для создания динамической библиотеки:

Оболочка
$ 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);
}
Оболочка
$ 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);
}
Оболочка
$ zig build
$ ./zig-out/bin/test
all your base are belong to us

См. также:

  • Цели
  • Система сборки Zig

WebAssembly

Zig поддерживает создание WebAssembly «из коробки».

Самостоятельно работающий

Для сред разработки, таких как веб-браузер и nodejs, создайте исполняемый файл с использованием целевой системы freestanding OS. Вот пример запуска кода Zig, скомпилированного в WebAssembly с помощью nodejs.

math.zig
extern fn print(i32) void;

export fn add(a: i32, b: i32) void {
    print(a + b);
}
Оболочка
$ 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);
});
Оболочка
$ node test.js
The result is 3

WASI

Поддержка Zig для WebAssembly System Interface (WASI) находится в активной разработке. Пример использования стандартной библиотеки и чтения аргументов командной строки:

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

pub fn main() !void {
    var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
    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 });
    }
}
Оболочка
$ zig build-exe wasi_args.zig -target wasm32-wasi
Оболочка
$ wasmtime wasi_args.wasm 123 hello
0: wasi_args.wasm
1: 123
2: hello

Более интересный пример — извлечение списка преоткрытий из среды выполнения. Теперь это поддерживается в стандартной библиотеке с помощью 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(.{}){};
    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 });
    }
}
Оболочка
$ zig build-exe wasi_preopens.zig -target wasm32-wasi
Оболочка
$ wasmtime --dir=. wasi_preopens.wasm
0: stdin
1: stdout
2: stderr
3: .

Цели

Цель относится к компьютеру, который будет использоваться для запуска исполняемого файла. Она состоит из архитектуры процессора, набора включённых функций процессора, операционной системы, минимальной и максимальной версии операционной системы, ABI и версии ABI.

Zig — универсальный язык программирования, что означает, что он разработан для генерации оптимального кода для широкого набора целей. Команда zig targets предоставляет информацию обо всех целях, которые известны компилятору.

Если опция целевого компьютера не указана в компиляторе, по умолчанию выбирается хост-компьютер, что означает, что полученный исполняемый файл не подходит для копирования на другой компьютер. Для копирования исполняемого файла на другой компьютер, компилятору необходимо знать требования к целевому компьютеру через опцию -target.

Стандартная библиотека Zig (@import("std")) содержит платформенно-независимые абстракции, что делает один и тот же исходный код пригодным для многих целей. Некоторые коды более переносимы, чем другие. В целом, код Zig чрезвычайно переносим по сравнению с другими языками программирования.

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

Руководство по стилю

Эти соглашения по кодированию не навязываются компилятором, но они поставляются в этом руководстве вместе с компилятором, чтобы предоставить точку отсчёта, если кто-то захочет сослаться на авторитет по согласованному стилю кодирования Zig.

Избегайте избыточности в именах

Избегайте этих слов в именах типов:

  • Значение
  • Данные
  • Контекст
  • Менеджер
  • utils, misc или инициалы кого-либо

Всё является значением, все типы — данными, всё — контекстом, вся логика управляет состоянием. Ничто не передаётся с помощью слова, применимого ко всем типам.

Искушение использовать «утилиты», «разное» или инициалы кого-то — это неумение классифицировать или, чаще, чрезмерная классификация. Такие объявления могут жить в корне модуля, который их нуждается, без необходимости в именованном пространстве.

Избегайте избыточных имён в полных квалифицированных пространствах имён

Каждому объявлению компилятор присваивает полное квалифицированное пространство имён, создавая древовидную структуру. Выбирайте имена, основанные на полном квалифицированном пространстве имён, и избегайте избыточных сегментов имён.

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)});
}
Оболочка
$ zig build-exe redundant_fqn.zig
$ ./redundant_fqn
redundant_fqn.json.JsonValue

В этом примере «json» повторяется в полном квалифицированном пространстве имён. Решением является удаление Json из JsonValue. В этом примере у нас есть пустая структура с именем json, но помните, что файлы также действуют как часть полного квалифицированного пространства имён.

Этот пример является исключением из правила, указанного в Избегайте избыточности в именах. Значение типа было сведено к его сути: это значение json. Название не может быть более конкретным без искажения.

Отступы

  • Отступы: 4 пробела
  • Открывающие фигурные скобки на одной строке, если только вам не нужно переносить.
  • Если список элементов содержит более 2 элементов, помещайте каждый элемент на отдельную строку и используйте возможность добавления дополнительной запятой в конце.
  • Длина строки: стремитесь к 100; руководствуйтесь здравым смыслом.

Имена

Грубо говоря: camelCaseFunctionName, TitleCaseTypeName, snake_case_variable_name. Более точно:

  • Если x является type, то x должно быть TitleCase, за исключением случаев, когда это struct с 0 полями и никогда не должно быть экземпляризовано, в этом случае оно рассматривается как «пространство имён» и использует 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 и другим инструментам предоставлять более подробную справку.
  • Используйте слово предполагать для указания инвариантов, которые вызывают неопределённое поведение при нарушении.
  • Используйте слово утверждать для указания инвариантов, которые вызывают проверяемое на безопасность неопределённое поведение при нарушении.

Кодировка исходного кода

Исходный код 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 будут закодированы как LF при компиляции в программу Zig. 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 позволяет указателю иметь адрес ноль.
  • См. также allowzero
and
Булевый оператор and.
  • См. также Операторы
anyframe
anyframe может использоваться как тип для переменных, хранящих указатели на фреймы функций.
  • См. также Асинхронные функции
anytype
Параметры функции могут быть объявлены с использованием anytype вместо типа. Тип будет выведен при вызове функции.
  • См. также Вывод типа параметров функции
asm
asm начинает выражение встроенного ассемблера. Это позволяет напрямую управлять машинным кодом, генерируемым при компиляции.
  • См. также Ассемблер
async
async может использоваться перед вызовом функции для получения указателя на фрейм функции при ее приостановке.
  • См. также Асинхронные функции
await
await может использоваться для приостановки текущей функции до завершения фрейма, предоставленного после await. await копирует значение, возвращенное из фрейма целевой функции, в вызывающую функцию.
  • См. также Асинхронные функции
break
break может использоваться с меткой блока для возврата значения из блока. Также его можно использовать для выхода из цикла до естественного завершения итерации.
  • См. также Блоки, while, for
callconv
callconv может использоваться для указания соглашения о вызовах в типе функции.
  • См. также Функции
catch
catch может использоваться для оценки выражения, если выражение перед ним оценивается как ошибка. Выражение после catch может необязательно захватить значение ошибки.
  • См. также catch, Операторы
comptime
comptime перед объявлением может использоваться для маркировки переменных или параметров функций как известных на этапе компиляции. Также его можно использовать для гарантии выполнения выражения на этапе компиляции.
  • См. также comptime
const
const объявляет переменную, которая не может быть изменена. Используется как атрибут указателя, он обозначает, что значение, на которое указывает указатель, не может быть изменено.
  • См. также Переменные
continue
continue может использоваться в цикле для возврата к началу цикла.
  • См. также while, for
defer
defer выполнит выражение, когда поток управления покинет текущий блок.
  • См. также defer
else
else может использоваться для предоставления альтернативной ветви для выражений if, switch, while, и for.
  • Если используется после выражения if, ветвь else будет выполнена, если значение теста возвращает false, null или ошибку.
  • Если используется в выражении switch, ветвь else будет выполнена, если значение теста не соответствует ни одному другому случаю.
  • Если используется после выражения цикла, ветвь else будет выполнена, если цикл завершится без прерывания.
  • См. также if, switch, while, for
enum
enum определяет тип перечисления.
  • См. также перечисление
errdefer
errdefer выполнит выражение, когда поток управления покинет текущий блок, если функция возвращает ошибку, выражение errdefer может захватить значение без обертки.
  • См. также errdefer
error
error определяет тип ошибки.
  • См. также Ошибки
export
export делает функцию или переменную внешне видимой в сгенерированном объектом файле. Экспортируемые функции по умолчанию используют соглашение о вызовах C.
  • См. также Функции
extern
extern может использоваться для объявления функции или переменной, которая будет разрешена во время линковки (при статической линковке) или во время выполнения (при динамической линковке).
  • См. также Функции
fn
fn объявляет функцию.
  • См. также Функции
for
Выражение for может использоваться для итерации по элементам среза, массива или кортежа.
  • См. также for
if
Выражение if может проверять булевы выражения, значения с возможностью отсутствия или союзы ошибок. Для значений с возможностью отсутствия или союзов ошибок, выражение if может захватить значение без обертки.
  • См. также if
inline
inline может использоваться для маркировки выражения цикла, таким образом, оно будет развёрнуто на этапе компиляции. Также его можно использовать для принудительного встраивания функции во все места вызова.
  • См. также inline while, inline for, Функции
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 случаи могут захватывать значения полей размеченного союза.
  • См. также switch
test
Ключевое слово test можно использовать для обозначения блоков кода верхнего уровня, используемых для проверки соответствия ожидаемому поведению.
  • См. также Тест Zig
threadlocal
threadlocal можно использовать для указания переменной как локальной для потока.
  • См. также Переменные, локальные для потока
try
try вычисляет выражение объединения ошибок. Если это ошибка, то функция возвращает управление с той же ошибкой. В противном случае выражение приводит к распакованному значению.
  • См. также try
union
union определяет объединение.
  • См. также union
unreachable
unreachable может использоваться для утверждения, что поток управления никогда не достигнет определённой точки. В зависимости от режима сборки, unreachable может вызвать панику.
  • Вызывает панику в режимах Debug и ReleaseSafe, или при использовании zig test.
  • Не вызывает панику в режимах ReleaseFast и ReleaseSmall.
  • См. также unreachable
usingnamespace
usingnamespace — это объявление верхнего уровня, которое импортирует все публичные объявления операнда (который должен быть структурой, объединением или перечислением) в текущую область видимости.
  • См. также usingnamespace
var
var объявляет переменную, которая может быть изменена.
  • См. также Переменные
volatile
volatile можно использовать для обозначения того, что загрузка или сохранение указателя имеют побочные эффекты. Также может изменить выражение встроенного ассемблера, чтобы обозначить, что оно имеет побочные эффекты.
  • См. также volatile, Ассемблер
while
Выражение while может использоваться для многократной проверки булевого, необязательного или объединения выражений ошибок, и прекращения цикла, когда выражение принимает значение false, null или ошибку соответственно.
  • См. также while

Приложение

Контейнеры

Контейнер в 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

Принципы

  • Точно передавайте намерения.
  • Крайние случаи важны.
  • Предпочитайте чтение кода написанию кода.
  • Только один очевидный способ сделать вещи.
  • Сбои во время выполнения предпочтительнее, чем ошибки.
  • Ошибки компиляции предпочтительнее сбоев во время выполнения.
  • Поэтапные улучшения.
  • Избегайте локальных максимумов.
  • Сокращайте количество информации, которую нужно запоминать.
  • Фокусируйтесь на коде, а не на стиле.
  • Выделение ресурсов может завершиться неудачей; освобождение ресурсов должно завершиться успехом.
  • Память — это ресурс.
  • Вместе мы служим пользователям.

© 2015–2024, Zig contributors
Licensed under the MIT License.
https://ziglang.org/documentation/0.13.0/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API