Zig — компилируемый язык системного программирования, который сочетает низкоуровневый контроль с современной эргономикой. В 2026 году стабильная ветка — 0.17.1; язык пока не достиг версии 1.0, зато уже используется в инфраструктурных проектах, базах данных и инструментах сборки. Ниже — установка, работа с ошибками и памятью, comptime, сборка, кросс-компиляция и честный разбор ограничений.

Чем Zig отличается

В языке нет скрытых механизмов: исключений, перегрузки операторов и макросов препроцессора. Нет и сборщика мусора — память выделяется через явно переданный аллокатор. Ошибки возвращаются как значения через error union, а defer и errdefer гарантируют освобождение ресурсов при выходе из области видимости. Проверки границ и переполнения включены в режимах Debug и ReleaseSafe, а в ReleaseFast их можно отключить ради скорости.

  • Comptime заменяет шаблоны и часть кодогенерации: вычисления выполняются на этапе компиляции.
  • C ABI без прослоек: функции C вызываются напрямую, заголовки подключаются через @cImport.
  • Кросс-компиляция из коробки: компилятор несёт заголовки libc и умеет собирать под другие архитектуры.
  • Один инструмент: zig build, zig test, zig fmt и C-компилятор zig cc.

Установка

В репозиториях дистрибутивов версия часто отстаёт, поэтому для работы ставьте официальный архив. Скачайте сборку для своей архитектуры, распакуйте в /opt и добавьте символическую ссылку.

curl -LO https://ziglang.org/download/0.17.1/zig-x86_64-linux-0.17.1.tar.xz
tar -xf zig-x86_64-linux-0.17.1.tar.xz
sudo mv zig-x86_64-linux-0.17.1 /opt/zig
sudo ln -sf /opt/zig/zig /usr/local/bin/zig
zig version

Для ARM64 используется файл zig-aarch64-linux-0.17.1.tar.xz. Для CI фиксируйте версию и храните архив рядом с проектом.

Первая программа

Создайте src/main.zig. Точка входа может возвращать !void, если возможна ошибка. Печать в stderr через std.debug.print удобна для утилит и не требует настройки буферов.

const std = @import("std");

pub fn main() void {
    const values = [_]u64{ 4, 8, 15, 16, 23, 42 };
    var sum: u64 = 0;
    for (values) |v| sum += v;
    std.debug.print("сумма: {d}\n", .{sum});
}

Запуск: zig run src/main.zig. Формат {d} подставляет целое, {s} — строку. Компилятор не прощает неиспользуемых переменных и неявных преобразований.

Ошибки как значения

Свой набор ошибок объявляется через error{...}. Функция возвращает ConfigError!u16: либо число, либо ошибку. try пробрасывает ошибку вызывающему, catch обрабатывает её на месте.

const ConfigError = error{InvalidPort};

fn parsePort(text: []const u8) ConfigError!u16 {
    const port = std.fmt.parseInt(u16, text, 10) catch return ConfigError.InvalidPort;
    if (port == 0) return ConfigError.InvalidPort;
    return port;
}

pub fn main() void {
    const port = parsePort("8080") catch |err| {
        std.debug.print("ошибка: {s}\n", .{@errorName(err)});
        return;
    };
    std.debug.print("порт: {d}\n", .{port});
}

Такой подход удобен для системного кода: в сигнатуре видно, какие сбои возможны, а стек не разворачивается исключениями. errdefer выполняет уборку только при уходе с ошибкой, обычный defer — всегда.

Память и comptime

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

fn zeroed(allocator: std.mem.Allocator, len: usize) ![]u8 {
    const buf = try allocator.alloc(u8, len);
    errdefer allocator.free(buf);
    @memset(buf, 0);
    return buf;
}

pub fn main() !void {
    const buf = try zeroed(std.heap.page_allocator, 4096);
    defer std.heap.page_allocator.free(buf);
    std.debug.print("размер: {d}\n", .{buf.len});
}

Comptime позволяет строить типы по параметрам. Например, кольцевой буфер фиксированной ёмкости не требует динамической памяти и проверяется на этапе компиляции.

fn Ring(comptime capacity: usize) type {
    return struct {
        items: [capacity]u32 = [_]u32{0} ** capacity,
        head: usize = 0,

        pub fn push(self: *@This(), value: u32) void {
            self.items[self.head] = value;
            self.head = (self.head + 1) % capacity;
        }
    };
}

const Ring8 = Ring(8);

Такой код разворачивается в обычные структуры и циклы, без виртуальных таблиц и боксинга.

Сборка и тесты

Современный build.zig описывает цели декларативно. Шаблон ниже создаёт исполняемый файл и отдельный шаг тестов; после zig init сверьте структуру с актуальной документацией, потому что API сборки меняется между выпусками.

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name = "ring",
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.installArtifact(exe);

    const run_cmd = b.addRunArtifact(exe);
    b.step("run", "Запустить").dependOn(&run_cmd.step);

    const tests = b.addTest(.{
        .root_module = b.createModule(.{
            .root_source_file = b.path("src/main.zig"),
            .target = target,
            .optimize = optimize,
        }),
    });
    b.step("test", "Тесты").dependOn(&b.addRunArtifact(tests).step);
}

Команды: zig build, zig build run, zig build test, zig build -Doptimize=ReleaseSafe. Тесты пишутся рядом с кодом в блоках test "имя" { ... } и используют std.testing. Для проверки синтаксиса без сборки есть zig ast-check, для форматирования — zig fmt.

Кросс-компиляция и C

Zig умеет собирать бинарники под другую архитектуру без отдельного toolchain. Статическая сборка под Linux ARM64 — одна команда.

zig build-exe src/main.zig -O ReleaseSafe
zig build-exe src/main.zig -target aarch64-linux-musl -O ReleaseSafe
zig cc -O2 -o app main.c -lm
zig c++ -O2 -o app main.cpp

zig cc понимает флаги GCC/Clang и подходит для сборки старых C-проектов, когда нужен свежий компилятор. Для вызова C-библиотеки из Zig подключите заголовок через @cImport, а в build.zig добавьте exe.linkLibC() и exe.linkSystemLibrary("z"). Это заметно проще, чем писать собственные обёртки, но требует установленных dev-пакетов.

Ограничения и где применять

До 1.0 язык и стандартная библиотека меняются: код, написанный под 0.13, может не собраться на 0.17. Особенно это касается ввода-вывода и интерфейсов аллокаторов — перед обновлением читайте release notes и фиксируйте версию компилятора. Экосистема меньше, чем у Rust или Go, а готовых библиотек для прикладных задач пока немного.

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

Итог

Zig даёт явные аллокаторы, ошибки как значения, comptime и встроенную кросс-компиляцию без скрытых затрат. Стабильная версия 0.17.1 уже пригодна для инструментов и экспериментов, но требует дисциплины при обновлениях. Проверьте сборку на своей версии, напишите тесты и зафиксируйте toolchain — тогда язык окупается предсказуемостью рантайма.

Насколько публикация полезна?

Нажмите на звезду, чтобы оценить!

Средняя оценка 0 / 5. Количество оценок: 0

Оценок пока нет. Поставьте оценку первым.

Поделиться: Telegram ВКонтакте