Zig почти всегда представляют в паре с Rust: «ещё один безопасный системный язык». Это удобная, но неверная рамка. Rust и Zig делают противоположные ставки: Rust добавляет сложность системы типов, чтобы гарантировать безопасность памяти на компиляции; Zig, наоборот, убирает всё скрытое и оставляет ручное управление — ради простоты и полного контроля. Понять Zig — значит понять именно эту ставку.
Эта статья — не туториал по синтаксису, а карта: где Zig находится среди C, Rust и C++, что делает его особенным и где он пока не лучший выбор.
В статье
- Что такое Zig и его философия
- Никакого скрытого поведения
- comptime: одна идея вместо макросов, дженериков и рефлексии
- Явные аллокаторы и ошибки как значения
- Zig как C/C++ тулчейн
- Zig против C, Rust и C++
- Где Zig уместен, а где нет
- Как собрать, протестировать и на что посмотреть
- Зрелость и риски
- Итог: как читать позиционирование Zig
Примеры кода проверены на Zig 0.16 (стабильный релиз на момент публикации). Язык до 1.0, API стандартной библиотеки и системы сборки меняется между версиями — на другой версии сигнатуры могут отличаться.
Что такое Zig и его философия
Zig — компилируемый системный язык общего назначения, создаваемый как современная замена C, а не как «улучшенный C++» или «более простой Rust». Его цель формулируется прямо: помогать поддерживать надёжное, оптимальное и переиспользуемое ПО. Отсюда — минимализм: небольшой язык, который можно удержать в голове целиком, без препроцессора, без макросов, без перегрузки операторов и без исключений.
Ключевая философия — всё явно. Если в коде происходит выделение памяти, вызов или переход управления — это видно в исходнике. Именно из этого принципа вытекают все остальные особенности языка.
Никакого скрытого поведения
Zig сознательно запрещает механизмы, которые в C++ прячут стоимость и поток управления:
- Нет скрытого потока управления. Нет исключений: функция либо возвращает значение, либо ошибку (см. ниже). Нет перегрузки операторов, скрывающей вызовы функций. Если вы видите
a + b— это сложение, а не чужой код. - Нет скрытых аллокаций. Стандартная библиотека никогда не выделяет память сама — аллокатор передаётся явным аргументом. Это делает Zig пригодным там, где аллокации недопустимы (embedded, ядра, аллокатор-чувствительные системы).
- Нет препроцессора и макросов. Метапрограммирование делается на самом языке через
comptime.
Это прямой контраст с C++, где throw, RAII-деструкторы, перегрузки и неявные преобразования прячут и стоимость, и управление.
comptime: одна идея вместо макросов, дженериков и рефлексии
comptime — центральная идея Zig. Это выполнение обычного Zig-кода на этапе компиляции. Одним механизмом Zig закрывает то, для чего другим языкам нужны отдельные подсистемы: макросы, шаблоны/дженерики и рефлексия.
Дженерик — это просто функция, принимающая тип как comptime-параметр:
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
pub fn main() void {
const a = max(i32, 3, 7); // тип разрешается на компиляции
const b = max(f64, 2.5, 1.0);
_ = a;
_ = b;
}Типы в Zig — значения первого класса на этапе компиляции, поэтому type можно передавать, возвращать и вычислять. Таблицы, специализация под платформу, кодогенерация — всё это обычный код в comptime-блоке, а не отдельный язык макросов.
Явные аллокаторы и ошибки как значения
Память в Zig — ручная, но управление ею явное и структурированное. Аллокатор — обычный параметр; освобождение выражается через defer/errdefer, привязанные к области видимости.
const std = @import("std");
fn sumRange(allocator: std.mem.Allocator, n: usize) !u64 {
const buf = try allocator.alloc(u64, n); // может вернуть error.OutOfMemory
defer allocator.free(buf); // освободится при любом выходе из функции
for (buf, 0..) |*x, i| x.* = i;
var total: u64 = 0;
for (buf) |x| total += x;
return total;
}Тип !u64 — это error union: функция возвращает либо число, либо ошибку (здесь — error.OutOfMemory от аллокатора). try пробрасывает ошибку вверх, catch обрабатывает её, а errdefer освобождает ресурс только на пути ошибки:
fn create(allocator: std.mem.Allocator) !*Node {
const node = try allocator.create(Node);
errdefer allocator.destroy(node); // сработает, только если ниже ошибка
node.* = try Node.init(allocator);
return node; // успех — errdefer не срабатывает
}Важная честность в позиционировании: у Zig нет borrow checker. Он не доказывает отсутствие use-after-free на компиляции, как Rust. Вместо этого в безопасных режимах сборки (Debug, ReleaseSafe) Zig вставляет рантайм-проверки — выход за границы, переполнение, разыменование null-оптионала, — а отладочный аллокатор ловит часть утечек и use-after-free. Это осознанный компромисс: меньше гарантий, но проще модель и полный контроль.
Zig как C/C++ тулчейн
Самая недооценённая часть позиционирования: Zig — это ещё и тулчейн для C и C++. C-interop встроен в язык, без генераторов обёрток:
const c = @cImport({
@cInclude("stdio.h");
});
pub fn main() void {
_ = c.printf("hello from C\n");
}Для сборки такого файла нужна линковка libc — zig build-exe main.zig -lc. А компилятор Zig несёт в себе clang и исходники нескольких libc, что даёт кросс-компиляцию из коробки — под широкий набор целей без установки внешних тулчейнов:
# один и тот же исходник — под разные платформы, без стороннего тулчейна
zig build-exe main.zig -target aarch64-linux-musl
zig build-exe main.zig -target x86_64-windows
# zig умеет компилировать C и C++ — как замена gcc/clang и g++/clang++
zig cc -o app app.c
zig c++ -o app app.cppИменно поэтому Zig используют даже те, кто не пишет на Zig: zig cc и zig c++ как надёжный кросс-компилятор для C/C++-проектов. Это сильный аргумент позиционирования «замена C-тулчейна», отдельный от языка.
Zig против C, Rust и C++
| Zig | C | Rust | C++ | |
|---|---|---|---|---|
| Память | ручная, явные аллокаторы | ручная | владение + borrow checker | ручная / RAII |
| Безопасность памяти | рантайм-проверки в safe-режимах | нет | гарантии на компиляции | нет |
| Метапрограммирование | comptime |
препроцессор | макросы + generics + traits | шаблоны |
| Скрытые издержки | нет | нет | немного (Drop, паники) |
много (исключения, RTTI, перегрузки) |
| C-interop | встроенный, без обёрток | родной | через FFI/bindgen | родной |
| Кросс-компиляция | встроенная, first-class | внешние тулчейны | rustup targets |
внешние тулчейны |
| Зрелость | pre-1.0 | стандарт | стабильный | стандарт |
Главный водораздел — по строке «безопасность памяти»: Rust даёт гарантии ценой сложности системы типов; Zig оставляет ответственность программисту ценой рантайм-проверок и дисциплины. C и C++ не дают ни того, ни другого.
Где Zig уместен, а где нет
Хорошо ложится:
- Замена C там, где нужен современный язык, но не нужна (или неприемлема) сложность Rust
- Embedded и системы без аллокаций: явные аллокаторы — прямое преимущество
- Кросс-компиляция и сборка C/C++-проектов (
zig cc) — даже без единой строки на Zig - Постепенная миграция C-кодовой базы: можно вкраплять Zig, вызывая C напрямую
Стоит выбрать другое:
- Нужны гарантии безопасности памяти на компиляции при работе в команде с недоверенным кодом → Rust
- Нужна большая зрелая экосистема и стабильный API прямо сейчас → C++/Rust/Go
- Сетевые сервисы с быстрой разработкой, где системный контроль не нужен → Go
Как собрать, протестировать и на что посмотреть
У Zig нет отдельной системы сборки и тест-раннера — и то, и другое встроено в тулчейн.
Собрать. Компиляция и запуск — одной командой; режим сборки прямо влияет на безопасность:
zig run main.zig # скомпилировать и сразу запустить
zig build-exe main.zig # собрать исполняемый файл
zig build-exe main.zig -O Debug # по умолчанию: safety-проверки включены
zig build-exe main.zig -O ReleaseSafe # оптимизация + проверки
zig build-exe main.zig -O ReleaseFast # максимум скорости, без проверок
zig build-exe main.zig -O ReleaseSmall # минимум размераДля проектов есть система сборки на самом Zig — файл build.zig (плюс build.zig.zon для зависимостей и пакетного менеджера), а команды — zig build, zig build run, zig build test.
Протестировать. Тесты — часть языка: блоки test пишутся прямо в исходнике, рядом с кодом.
const std = @import("std");
fn add(a: i32, b: i32) i32 {
return a + b;
}
test "add складывает" {
try std.testing.expectEqual(@as(i32, 5), add(2, 3));
}Запуск — zig test main.zig или zig build test. Показательная деталь: std.testing.allocator автоматически ловит утечки памяти в тестах — прямое следствие философии явных аллокаторов.
На что посмотреть. Позиционирование виднее всего по реальным проектам на Zig:
- TigerBeetle — финансовая БД, эталон «надёжного системного ПО».
- Bun — JS/TS-рантайм (тот самый из сравнения рантаймовСкоро), крупная Zig-кодовая база.
- Ghostty — терминальный эмулятор Митчелла Хашимото.
- River (Wayland-композитор), Mach (игровой движок), плюс сам компилятор и стандартная библиотека Zig.
Запускаемый пример к статье — hello, comptime-дженерик и тест — в digital-cookbook → zig/hello-comptime.
Зрелость и риски
Zig ещё не достиг 1.0. Это значит: язык и стандартная библиотека меняются, случаются ломающие изменения между версиями, экосистема пакетов и менеджер пакетов молоды, а инструментов и готовых библиотек кратно меньше, чем у C++/Rust. Для продакшена это реальный фактор риска — пиновать версию, следить за миграциями, быть готовым дописывать то, чего нет в экосистеме.
При этом стабильная часть — компилятор как C/C++-тулчейн — уже применяется в проде многими, кто на самом Zig ещё не пишет. Это разумная точка входа: попробовать zig cc и кросс-компиляцию, не переписывая код.
Итог: как читать позиционирование Zig
Zig — не «Rust попроще» и не «C++ без легаси». Это современная замена C с одной последовательной идеей: ничего скрытого. Явные аллокаторы, comptime вместо макросов и шаблонов, ошибки как значения, встроенные C-interop и кросс-компиляция — всё это следствия одного принципа. Взамен вы не получаете гарантий безопасности памяти уровня Rust: это сознательный размен на простоту и контроль.
Если вам нужен управляемый, предсказуемый системный язык и вы готовы взять ответственность за память на себя — Zig ложится идеально. Если критична доказанная безопасность или зрелая экосистема сегодня — пока рано. А zig cc стоит попробовать в любом случае: это лучший бесплатный кросс-компилятор, который у вас, возможно, уже под рукой.
Комментарии