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 — тогда язык окупается предсказуемостью рантайма.