Zig: место среди C, Rust и C++

Где на карте системных языков находится Zig: почему это замена C, а не конкурент Rust, что дают comptime и явные аллокаторы, чем силён C-interop и кросс-компиляция, и где Zig пока не стоит брать

Zig почти всегда представляют в паре с Rust: «ещё один безопасный системный язык». Это удобная, но неверная рамка. Rust и Zig делают противоположные ставки: Rust добавляет сложность системы типов, чтобы гарантировать безопасность памяти на компиляции; Zig, наоборот, убирает всё скрытое и оставляет ручное управление — ради простоты и полного контроля. Понять Zig — значит понять именно эту ставку.

Эта статья — не туториал по синтаксису, а карта: где Zig находится среди C, Rust и C++, что делает его особенным и где он пока не лучший выбор.

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 стоит попробовать в любом случае: это лучший бесплатный кросс-компилятор, который у вас, возможно, уже под рукой.

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

Обсуждение в Telegram

Присоединиться →

Комментарии