Zig: язык системного программирования Концепции Official Docs | Learn | Zig.Guide
Что такое ZigZig — компилируемый язык без GC, скрытых аллокаций и скрытого control flow.
Цель : замена C/C++ с лучшей безопасностью, читаемостью и tooling.
Компиляция : .zig → IR → (LLVM backend | x86 self-hosted backend) → native binary
Ключевые принципы :
Явное лучше неявного (нет скрытых аллокаций, скрытых control flow)Comptime вместо макросов и шаблоновАллокатор — параметр, а не глобальное состояниеОшибки — значения (error unions), не исключенияНет оператора ||, нет скрытых приведений типов Один и тот же синтаксис для comptime и runtime кода Текущая стабильная версия : 0.15.2 (октябрь 2025)
В разработке : 0.16.0-dev (новый async I/O, fibers)
Цель 1.0 : 2026, осталось 4 приоритета: производительность, полировка языка, качество std, формальная спецификация.
Версия Дата Основные изменения 0.13.0 июнь 2024 LLVM 18, rework std.Progress 0.14.0 март 2025 labeled switch, –watch, incremental compilation 0.15.1 август 2025 x86 backend по умолчанию, “Writergate” I/O overhaul 0.15.2 октябрь 2025 багфикс-патч 0.16.0-dev — std.Io, fibers, async/await возвращается
Установка # Arch
sudo pacman -S zig
# Ubuntu / Debian (через snap или вручную)
snap install zig --classic --beta
# или скачать бинарник
curl -LO https://ziglang.org/download/0.15.2/zig-linux-x86_64-0.15.2.tar.xz
tar xf zig-linux-x86_64-0.15.2.tar.xz
sudo mv zig-linux-x86_64-0.15.2 /opt/zig
sudo ln -s /opt/zig/zig /usr/local/bin/zig
# macOS
brew install zig
# Проверка
zig version # 0.15.2
zig env # полная информация о среде Создание проекта mkdir myproject && cd myproject
zig init
# Создаёт:
# build.zig — конфигурация сборки (на Zig)
# build.zig.zon — манифест зависимостей
# src/main.zig — точка входа
# src/root.zig — библиотечный модуль
# Сборка и запуск
zig build # собрать
zig build run # собрать и запустить
zig build test # запустить тесты
# Быстрый запуск файла без build.zig
zig run src/main.zig
# Кросс-компиляция
zig build -Dtarget= aarch64-linux-gnu
zig build -Dtarget= x86_64-windows-gnu
# Режимы оптимизации
zig build -Doptimize= Debug # по умолчанию, с проверками
zig build -Doptimize= ReleaseSafe # оптимизирован, с проверками
zig build -Doptimize= ReleaseFast # максимум скорости
zig build -Doptimize= ReleaseSmall # минимум размера Система типов Примитивные типы // Целые числа — точная ширина
const a: u8 = 255 ;
const b: i32 = - 42 ;
const c: u64 = 0 ;
const d: usize = @sizeOf(u64 ); // платформо-зависимый размер (как size_t)
const e: isize = - 1 ; // знаковый usize
// Числа с плавающей точкой
const f: f32 = 3.14 ;
const g: f64 = 2.718281828 ;
// Bool
const h: bool = true ;
// Comptime integer — без фиксированной ширины, только в comptime
const big: comptime_int = 1 << 128 ;
// Void — тип без значения (как unit)
const v: void = {};Массивы и слайсы // Массив — фиксированный размер, на стеке
const arr = [_]u32 { 1 , 2 , 3 , 4 }; // тип: [4]u32
const zeros: [64 ]u8 = @splat(0 ); // заполнить нулями (с 0.14)
const matrix = [3 ][3 ]f32 { // многомерный
.{ 1 , 0 , 0 },
.{ 0 , 1 , 0 },
.{ 0 , 0 , 1 },
};
// Слайс — fat pointer (указатель + длина)
const slice: []const u32 = arr[1 .. 3 ]; // элементы [1] и [2]
const all: []const u32 = & arr; // весь массив как слайс
// Sentinel-terminated slice — как C-строка, но безопаснее
const hello: [:0 ]const u8 = "hello" ; // нуль-терминированный
const raw: [* :0 ]const u8 = hello.ptr; // сырой указатель с sentinel
// Итерация
for (arr) | val| {
_ = val;
}
for (arr, 0 .. ) | val, i| { // с индексом
_ = val;
_ = i;
} Optional типы // ?T — значение или null. Для указателей — без дополнительной памяти.
var maybe: ?u32 = null ;
maybe = 42 ;
// Развёртывание через if
if (maybe) | val| {
// val = 42
_ = val;
} else {
// null
}
// orelse — значение по умолчанию
const value = maybe orelse 0 ;
// .? — unwrap с паникой если null (только в debug-сборках полезно)
const forced = maybe.? ;
_ = forced; Error union типы // Именованный набор ошибок
const FileError = error {
NotFound,
AccessDenied,
DiskFull,
};
// Функция, возвращающая ошибку или значение
fn openConfig (path: []const u8 ) FileError! Config {
_ = path;
return error .NotFound;
}
// !T — inferred error set (компилятор выводит набор ошибок)
fn doWork () ! void {
// ошибки автоматически собираются из вызываемых функций
} Структуры const Vec3 = struct {
x: f32 ,
y: f32 ,
z: f32 = 0.0 , // значение по умолчанию
const Self = @This();
// Метод — первый аргумент self
pub fn length (self: Self) f32 {
return @sqrt(self.x * self.x + self.y * self.y + self.z * self.z);
}
// "Статический" метод — без self
pub fn zero () Self {
return .{ .x = 0 , .y = 0 , .z = 0 };
}
// Мутирующий метод — указатель на self
pub fn scale (self: * Self, factor: f32 ) void {
self.x *= factor;
self.y *= factor;
self.z *= factor;
}
};
// Инициализация
const v = Vec3{ .x = 1.0 , .y = 2.0 }; // z = 0.0 по умолчанию
const origin = Vec3.zero ();
_ = origin;
var mutable = Vec3{ .x = 1.0 , .y = 1.0 };
mutable.scale (2.0 ); Enum const Color = enum (u8 ) {
red = 0 ,
green = 1 ,
blue = 2 ,
pub fn isWarm (self: Color) bool {
return self == .red;
}
};
const c = Color.green;
const val: u8 = @intFromEnum(c); // 1
const back = @enumFromInt(val); // Color.green (тип выводится)
_ = back; Tagged union const Token = union (enum ) {
number: f64 ,
string: []const u8 ,
eof, // без payload
pub fn isEnd (self: Token) bool {
return self == .eof;
}
};
const tok = Token{ .number = 3.14 };
// Pattern matching через switch
switch (tok) {
.number => | n| std.debug.print ("number: {d} \n " , .{n}),
.string => | s| std.debug.print ("string: {s} \n " , .{s}),
.eof => std.debug.print ("end \n " , .{}),
} Обработка ошибок Zig использует error union (!T) и error set вместо исключений. Нет скрытого control flow: каждая точка возможной ошибки явно помечена.
try, catch, errdefer const std = @import("std" );
fn readFile (allocator: std.mem.Allocator, path: []const u8 ) ! []u8 {
// try — развернуть значение или вернуть ошибку вызывающему
const file = try std.fs.cwd ().openFile (path, .{});
defer file.close (); // defer — выполнится при выходе из scope
// errdefer — выполнится ТОЛЬКО если функция вернёт ошибку
const buf = try allocator.alloc (u8 , 4096 );
errdefer allocator.free (buf);
const bytes_read = try file.readAll (buf);
return buf[0 .. bytes_read];
// если всё ok — errdefer НЕ выполнится, вызывающий владеет buf
}
fn processFile (path: []const u8 ) void {
const allocator = std.heap.page_allocator;
// catch — обработать ошибку локально
const data = readFile (allocator, path) catch | err| {
std.debug.print ("ошибка: {} \n " , .{err});
return ;
};
defer allocator.free (data);
// catch с значением по умолчанию
const config = readFile (allocator, "config.txt" ) catch & [_]u8 {};
_ = config;
_ = data;
} errdefer с захватом ошибки fn initialize (allocator: std.mem.Allocator) !* Resource {
const r = try allocator.create (Resource);
errdefer | err| {
std.log.err ("init failed: {}" , .{err});
allocator.destroy (r);
}
try r.setup ();
return r;
} switch по ошибкам fn openConfig (path: []const u8 ) ! Config {
const file = std.fs.cwd ().openFile (path, .{}) catch | err| switch (err) {
error .FileNotFound => return Config.defaults (),
error .AccessDenied => {
std.log.err ("нет доступа к {s}" , .{path});
return err;
},
else => return err,
};
defer file.close ();
_ = file;
return Config.defaults ();
} Error return traces В debug-сборках Zig предоставляет трассировку возврата ошибок, которая показывает цепочку try, через которую прошла ошибка. Это не stack trace, а trace пути ошибки через код.
Аллокаторы Zig не имеет GC и скрытых аллокаций. Память управляется через явный интерфейс std.mem.Allocator, который передаётся как параметр.
Доступные аллокаторы const std = @import("std" );
pub fn main () ! void {
// 1. GeneralPurposeAllocator (GPA) — безопасный, ловит утечки и use-after-free
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer {
const check = gpa_state.deinit ();
if (check == .leak) @panic("memory leak detected" );
}
const gpa = gpa_state.allocator ();
// 2. ArenaAllocator — пакетное освобождение, идеален для запросов/фаз
var arena_state = std.heap.ArenaAllocator.init (std.heap.page_allocator);
defer arena_state.deinit (); // освобождает ВСЁ разом
const arena = arena_state.allocator ();
// 3. FixedBufferAllocator — без кучи, из стекового/статического буфера
var buf: [4096 ]u8 = undefined ;
var fba = std.heap.FixedBufferAllocator.init (& buf);
const fixed = fba.allocator ();
// 4. page_allocator — прямые страницы ОС, грубый
const page = std.heap.page_allocator;
// 5. c_allocator — обёртка над malloc/free для C interop
const c_alloc = std.heap.c_allocator;
_ = gpa;
_ = arena;
_ = fixed;
_ = page;
_ = c_alloc;
} Паттерны работы с памятью const std = @import("std" );
const Allocator = std.mem.Allocator;
// Функция, которая аллоцирует, принимает аллокатор параметром
fn createBuffer (allocator: Allocator, size: usize ) ! []u8 {
return try allocator.alloc (u8 , size);
}
// RAII через defer
fn processData (allocator: Allocator) ! void {
const buffer = try allocator.alloc (u8 , 1024 );
defer allocator.free (buffer); // гарантированное освобождение
const result = try allocator.create (Result);
errdefer allocator.destroy (result); // освобождение только при ошибке
try populateResult (result, buffer);
}
// Arena для request-scoped работы
fn handleRequest (parent: Allocator, body: []const u8 ) ! Response {
var arena_state = std.heap.ArenaAllocator.init (parent);
defer arena_state.deinit (); // все временные аллокации — разом
const arena = arena_state.allocator ();
const parsed = try parse (arena, body);
const validated = try validate (arena, parsed);
return try buildResponse (parent, validated); // результат — через parent
} Тестовый аллокатор // std.testing.allocator — ловит утечки в тестах
test "no leaks" {
const allocator = std.testing.allocator;
const data = try allocator.alloc (u8 , 100 );
defer allocator.free (data);
// если забыть defer — тест УПАДЁТ с информацией об утечке
} Comptime: метапрограммирование Comptime: выполнение кода во время компиляции. Тот же синтаксис, что и для runtime, но работает в compile-time. Заменяет макросы, шаблоны C++ и кодогенерацию.
Основы // Comptime-вычисление
const len = comptime blk: {
const s = "hello" ;
break :blk s.len;
};
// Comptime-параметр — основа обобщённого программирования (generics)
fn max (comptime T: type , a: T, b: T) T {
return if (a > b) a else b;
}
// Компилятор генерирует специализированный код для каждого типа
const result = max (u32 , 10 , 20 );
const float_result = max (f64 , 1.5 , 2.5 );Обобщённые структуры данных fn LinkedList (comptime T: type ) type {
return struct {
const Self = @This();
const Node = struct {
data: T,
next: ?* Node = null ,
};
head: ?* Node = null ,
len: usize = 0 ,
pub fn prepend (self: * Self, allocator: std.mem.Allocator, value: T) ! void {
const node = try allocator.create (Node);
node.* = .{ .data = value, .next = self.head };
self.head = node;
self.len += 1 ;
}
pub fn toSlice (self: Self, allocator: std.mem.Allocator) ! []T {
var result = try allocator.alloc (T, self.len);
var current = self.head;
var i: usize = 0 ;
while (current) | node| {
result[i] = node.data;
current = node.next;
i += 1 ;
}
return result;
}
};
}
// Использование — как шаблоны, но через обычный вызов функции
var list: LinkedList (i32 ) = .{};Интроспекция типов fn debugPrint (value: anytype ) void {
const T = @TypeOf(value);
const info = @typeInfo(T);
switch (info) {
// 0.15: теги в нижнем регистре, ключевые слова — через @""
.int => std.debug.print ("int: {} \n " , .{value}),
.float => std.debug.print ("float: {d} \n " , .{value}),
.pointer => std.debug.print ("pointer \n " , .{}),
.@"struct" => {
inline for (std.meta.fields (T)) | field| {
std.debug.print ("{s}: {any} \n " , .{
field.name,
@field(value, field.name),
});
}
},
else => std.debug.print ("other type \n " , .{}),
}
} Comptime строки // Генерация строк в compile-time
fn makeFieldName (comptime prefix: []const u8 , comptime index: usize ) []const u8 {
return prefix ++ std.fmt.comptimePrint ("{}" , .{index});
}
const names = comptime blk: {
var result: [3 ][]const u8 = undefined ;
for (0 .. 3 ) | i| {
result[i] = makeFieldName ("field_" , i);
}
break :blk result;
};
// names = { "field_0", "field_1", "field_2" } Ограничения comptime Нет I/O и системных вызовов Нет доступа к runtime-состоянию Нет inline assembly Циклы должны завершаться (нет бесконечных) Гермети чно, воспроизводимо, безопасно, кешируемо Управляющие конструкции if, while, for // if — также выражение
const abs_val = if (x < 0 ) - x else x;
// if с optional
if (optional_value) | val| {
// val развёрнут
_ = val;
} else {
// null
}
// while
var i: usize = 0 ;
while (i < 10 ) : (i += 1 ) { // "продолжение" — аналог for (;;i++)
if (i == 5 ) continue ;
if (i == 8 ) break ;
}
// while с optional
while (iterator.next ()) | item| {
process (item);
}
// for — по слайсам и ranges
for (items) | item| {
_ = item;
}
for (items, 0 .. ) | item, index| { // с индексом
_ = item;
_ = index;
}
for (0 .. 10 ) | i| { // range
_ = i;
} switch const result = switch (value) {
1 , 2 , 3 => "small" ,
4 .. .10 => "medium" , // range (включительно)
else => "large" ,
};
// switch по tagged union — exhaustive (компилятор проверяет все варианты)
switch (token) {
.number => | n| handleNumber (n),
.string => | s| handleString (s),
.eof => return ,
} Labeled switch (с 0.14), для state machines const State = enum { start, identifier, number, done };
fn tokenize (input: []const u8 ) void {
var i: usize = 0 ;
const state: State = .start;
// Labeled switch — continue переходит к другому case
// без overhead цикла (компилятор: unconditional branch)
switch (state) {
.start => {
if (i >= input.len) {
continue :sw .done;
}
if (input[i] >= 'a' and input[i] <= 'z' ) {
continue :sw .identifier;
}
if (input[i] >= '0' and input[i] <= '9' ) {
continue :sw .number;
}
continue :sw .done;
},
.identifier => {
i += 1 ;
continue :sw .start;
},
.number => {
i += 1 ;
continue :sw .start;
},
.done => return ,
}
} defer и errdefer fn doWork () ! void {
const resource = try acquire ();
defer release (resource); // ВСЕГДА выполнится при выходе из scope
const buffer = try allocate ();
errdefer free (buffer); // только если функция вернёт ошибку
try use (resource, buffer);
// если ok — errdefer НЕ выполнится
}
// Порядок: defer-ы выполняются в обратном порядке (LIFO)
{
defer std.debug.print ("3 \n " , .{});
defer std.debug.print ("2 \n " , .{});
defer std.debug.print ("1 \n " , .{});
}
// Вывод: 1, 2, 3 Стандартная библиотека Вывод (0.15, Writergate) const std = @import("std" );
pub fn main () ! void {
// 0.15: явный буфер, обязательный flush
var stdout_buf: [4096 ]u8 = undefined ;
var stdout_writer = std.fs.File.stdout ().writer (& stdout_buf);
const stdout = & stdout_writer.interface;
try stdout.print ("Hello, {s}! \n " , .{"world" });
try stdout.flush (); // КРИТИЧНО: без flush вывод потеряется
// Для отладки — std.debug.print (без буфера, сразу в stderr)
std.debug.print ("debug: {} \n " , .{42 });
} ArrayList (0.15, Unmanaged) const std = @import("std" );
pub fn main () ! void {
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer _ = gpa_state.deinit ();
const allocator = gpa_state.allocator ();
// 0.15: ArrayListUnmanaged — основной тип
// Аллокатор передаётся в каждый мутирующий вызов
var list: std.ArrayListUnmanaged (i32 ) = .{};
defer list.deinit (allocator);
try list.append (allocator, 42 );
try list.append (allocator, 99 );
try list.appendSlice (allocator, & .{ 1 , 2 , 3 });
// Чтение — без аллокатора
const last = list.pop ();
_ = last;
for (list.items) | item| {
_ = item;
}
// Сортировка
std.mem.sort (i32 , list.items, {}, std.sort.asc (i32 ));
} HashMap const std = @import("std" );
pub fn main () ! void {
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer _ = gpa_state.deinit ();
const allocator = gpa_state.allocator ();
// StringHashMap — ключи []const u8
var map = std.StringHashMapUnmanaged (u32 ){};
defer map.deinit (allocator);
try map.put (allocator, "one" , 1 );
try map.put (allocator, "two" , 2 );
if (map.get ("one" )) | val| {
std.debug.print ("one = {} \n " , .{val});
}
// AutoHashMap — для произвольных ключей
var auto_map = std.AutoHashMapUnmanaged (u64 , []const u8 ){};
defer auto_map.deinit (allocator);
try auto_map.put (allocator, 42 , "answer" );
// Итерация
var it = map.iterator ();
while (it.next ()) | entry| {
std.debug.print ("{s}: {} \n " , .{ entry.key_ptr.* , entry.value_ptr.* });
}
} Строки и работа с памятью const std = @import("std" );
pub fn main () void {
// Строки — просто []const u8
const hello: []const u8 = "Hello, World!" ;
_ = hello;
// Сравнение
const equal = std.mem.eql (u8 , "abc" , "abc" ); // true
_ = equal;
// Поиск
if (std.mem.indexOf (u8 , "hello world" , "world" )) | pos| {
_ = pos; // 6
}
// Начинается/заканчивается
const starts = std.mem.startsWith (u8 , "hello" , "hel" ); // true
_ = starts;
// Токенизация (разбиение по разделителю)
var it = std.mem.tokenizeScalar (u8 , "hello world foo" , ' ' );
while (it.next ()) | token| {
_ = token; // "hello", "world", "foo"
}
// Split (сохраняет пустые элементы)
var it2 = std.mem.splitScalar (u8 , "a,,b,c" , ',' );
while (it2.next ()) | part| {
_ = part; // "a", "", "b", "c"
}
} Форматирование строк const std = @import("std" );
pub fn main () ! void {
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer _ = gpa_state.deinit ();
const allocator = gpa_state.allocator ();
// Форматирование в аллоцированную строку
const msg = try std.fmt.allocPrint (allocator, "user_{d}_score_{d}" , .{ 42 , 100 });
defer allocator.free (msg);
// Форматирование в буфер (без аллокации)
var buf: [256 ]u8 = undefined ;
const result = try std.fmt.bufPrint (& buf, "x={d}, y={d}" , .{ 10 , 20 });
_ = result; // слайс из buf
// Спецификаторы формата
// {d} — decimal integer
// {x} — hex
// {b} — binary
// {s} — string ([]const u8)
// {any} — любой тип (debug format)
// {e} — float exponential
// {d:.2} — float с 2 знаками
} Файловый I/O const std = @import("std" );
pub fn main () ! void {
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer _ = gpa_state.deinit ();
const allocator = gpa_state.allocator ();
// Чтение файла целиком
const content = try std.fs.cwd ().readFileAlloc (allocator, "data.txt" , 1024 * 1024 );
defer allocator.free (content);
// Запись файла
try std.fs.cwd ().writeFile (.{
.sub_path = "output.txt" ,
.data = "Hello, file! \n " ,
});
// Построчное чтение
const file = try std.fs.cwd ().openFile ("data.txt" , .{});
defer file.close ();
var buf: [4096 ]u8 = undefined ;
var reader_buf: [4096 ]u8 = undefined ;
var reader = file.reader (& reader_buf);
while (try reader.interface.readUntilDelimiterOrEof (& buf, '\n' )) | line| {
_ = line;
}
// Работа с директориями
var dir = try std.fs.cwd ().openDir ("src" , .{ .iterate = true });
defer dir.close ();
var dir_it = dir.iterate ();
while (try dir_it.next ()) | entry| {
std.debug.print ("{s} ({s}) \n " , .{ entry.name, @tagName(entry.kind) });
}
} JSON const std = @import("std" );
const Config = struct {
host: []const u8 ,
port: u16 ,
debug: bool = false ,
};
pub fn main () ! void {
var gpa_state: std.heap.GeneralPurposeAllocator (.{}) = .init;
defer _ = gpa_state.deinit ();
const allocator = gpa_state.allocator ();
// Парсинг JSON в структуру
const json_str =
\\{"host": "localhost", "port": 8080, "debug": true}
;
const parsed = try std.json.parseFromSlice (Config, allocator, json_str, .{});
defer parsed.deinit ();
const config = parsed.value;
std.debug.print ("host={s} port={} \n " , .{ config.host, config.port });
// Сериализация структуры в JSON
var buf: [1024 ]u8 = undefined ;
var stream = std.io.fixedBufferStream (& buf);
try std.json.stringify (config, .{}, stream.writer ());
const json_output = stream.getWritten ();
_ = json_output;
} Потоки (Threads) const std = @import("std" );
fn worker (id: usize ) void {
std.debug.print ("Worker {} started \n " , .{id});
std.time.sleep (100 * std.time.ns_per_ms);
std.debug.print ("Worker {} done \n " , .{id});
}
pub fn main () ! void {
// Запуск потоков
var threads: [4 ]std.Thread = undefined ;
for (& threads, 0 .. ) |* t, i| {
t.* = try std.Thread.spawn (.{}, worker, .{i});
}
for (threads) | t| {
t.join ();
}
// Mutex
var mutex: std.Thread.Mutex = .{};
var shared_counter: u64 = 0 ;
var workers: [4 ]std.Thread = undefined ;
for (& workers) |* t| {
t.* = try std.Thread.spawn (.{}, struct {
fn run (m: * std.Thread.Mutex, counter: * u64 ) void {
for (0 .. 1000 ) | _| {
m.lock ();
defer m.unlock ();
counter.* += 1 ;
}
}
}.run, .{ & mutex, & shared_counter });
}
for (workers) | t| {
t.join ();
}
std.debug.print ("counter = {} \n " , .{shared_counter}); // 4000
} Сетевое программирование const std = @import("std" );
// TCP сервер
pub fn main () ! void {
const address = std.net.Address.initIp4 (.{ 127 , 0 , 0 , 1 }, 8080 );
var server = try address.listen (.{
.reuse_address = true ,
});
defer server.deinit ();
std.debug.print ("Listening on :8080 \n " , .{});
while (true ) {
const conn = try server.accept ();
defer conn.stream.close ();
var read_buf: [4096 ]u8 = undefined ;
var reader_buf: [4096 ]u8 = undefined ;
var reader = conn.stream.reader (& reader_buf);
const request = try reader.interface.readUntilDelimiterOrEof (& read_buf, '\n' );
_ = request;
var write_buf: [4096 ]u8 = undefined ;
var writer = conn.stream.writer (& write_buf);
try writer.interface.print ("HTTP/1.1 200 OK \r\n Content-Length: 5 \r\n\r\n Hello" , .{});
try writer.interface.flush ();
}
} Система сборки (build.zig) Zig использует декларативно-императивную систему сборки, написанную на самом Zig. Граф шагов (DAG) выполняется параллельно.
Базовый build.zig (0.15) const std = @import("std" );
pub fn build (b: * std.Build) void {
const target = b.standardTargetOptions (.{});
const optimize = b.standardOptimizeOption (.{});
// 0.15: createModule + root_module
const exe = b.addExecutable (.{
.name = "myapp" ,
.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);
run_cmd.step.dependOn (b.getInstallStep ());
if (b.args) | args| {
run_cmd.addArgs (args);
}
const run_step = b.step ("run" , "Run the application" );
run_step.dependOn (& run_cmd.step);
// Шаг тестирования
const unit_tests = b.addTest (.{
.root_module = b.createModule (.{
.root_source_file = b.path ("src/main.zig" ),
.target = target,
.optimize = optimize,
}),
});
const run_unit_tests = b.addRunArtifact (unit_tests);
const test_step = b.step ("test" , "Run unit tests" );
test_step.dependOn (& run_unit_tests.step);
} Библиотеки (0.15) // Статическая/динамическая библиотека
const lib = b.addLibrary (.{
.name = "mylib" ,
.linkage = .static, // или .dynamic
.root_module = b.createModule (.{
.root_source_file = b.path ("src/lib.zig" ),
.target = target,
.optimize = optimize,
}),
}); File system watching и incremental compilation # Непрерывная пересборка при изменении файлов (с 0.14)
zig build --watch
# С debounce
zig build --watch --debounce 200
# Инкрементальная компиляция (экспериментально)
zig build --watch -fincremental Менеджер пакетов (build.zig.zon) Децентрализованный, content-addressed. Зависимости указываются по URL, без центрального реестра.
Манифест // build.zig.zon
.{
.name = .myproject,
.version = "0.1.0" ,
.minimum_zig_version = "0.15.0" ,
.dependencies = .{
// Удалённая зависимость
.zap = .{
.url = "https://github.com/zigzap/zap/archive/refs/tags/v0.2.0.tar.gz" ,
.hash = "1220aababababababababababababababababababababababababababababababababab" ,
},
// Локальная зависимость
.mylib = .{
.path = "../mylib" ,
},
// Ленивая — скачивается только при использовании
.optional_dep = .{
.url = "https://example.com/dep.tar.gz" ,
.hash = "1220..." ,
.lazy = true ,
},
},
.paths = .{
"build.zig" ,
"build.zig.zon" ,
"src" ,
},
} Подключение зависимостей в build.zig pub fn build (b: * std.Build) void {
const target = b.standardTargetOptions (.{});
const optimize = b.standardOptimizeOption (.{});
const zap_dep = b.dependency ("zap" , .{
.target = target,
.optimize = optimize,
});
const exe = b.addExecutable (.{
.name = "myapp" ,
.root_module = b.createModule (.{
.root_source_file = b.path ("src/main.zig" ),
.target = target,
.optimize = optimize,
.imports = & .{
.{ .name = "zap" , .module = zap_dep.module ("zap" ) },
},
}),
});
b.installArtifact (exe);
} Работа с хешами # Добавить зависимость (автоматически вычислит хеш)
zig fetch --save https://github.com/user/repo/archive/v1.0.tar.gz
# Или поставить placeholder-хеш, zig build покажет правильный
zig build
# error: hash mismatch... expected 1220abc..., found 1220def...
# Скопировать found-хеш в build.zig.zon Реестры пакетов Zigistry , community-реестрЗависимости URL-based (git archives, tarballs), не привязаны к реестру C interop Zig обеспечивает first-class совместимость с C: может компилировать C/C++ код, линковать C-библиотеки, транслировать C-заголовки.
@cImport (текущий способ, запланирован перенос в build system) const c = @cImport({
@cDefine("_GNU_SOURCE" , {});
@cInclude("stdio.h" );
@cInclude("mylib.h" );
});
pub fn main () void {
_ = c.printf ("Hello from C \n " );
} C interop через build system (рекомендуемый) // build.zig
pub fn build (b: * std.Build) void {
const target = b.standardTargetOptions (.{});
const optimize = b.standardOptimizeOption (.{});
const exe = b.addExecutable (.{
.name = "myapp" ,
.root_module = b.createModule (.{
.root_source_file = b.path ("src/main.zig" ),
.target = target,
.optimize = optimize,
}),
});
// Линковка libc
exe.linkLibC ();
// Линковка системной библиотеки
exe.linkSystemLibrary ("sqlite3" );
// Компиляция C-файлов
exe.addCSourceFiles (.{
.files = & .{ "src/legacy.c" , "src/wrapper.c" },
.flags = & .{ "-Wall" , "-O2" },
});
// Include-пути
exe.addIncludePath (b.path ("include" ));
// Трансляция C-заголовка в Zig-модуль
const translate = b.addTranslateC (.{
.root_source_file = b.path ("include/myheader.h" ),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport ("myheader" , translate.createModule ());
b.installArtifact (exe);
} Calling conventions и ABI // Extern-функция с C calling convention
extern "c" fn printf (fmt: [* :0 ]const u8 , .. .) c_int ;
// Zig-функция, вызываемая из C
export fn zig_add (a: c_int , b: c_int ) c_int {
return a + b;
}
// Явный calling convention (0.15 синтаксис)
fn sigHandler (_: c_int ) callconv (.c) void {
// обработчик сигнала с C ABI
} Zig как C/C++ компилятор # Drop-in замена C-компилятора (с кросс-компиляцией из коробки)
zig cc -o hello hello.c
zig c++ -o hello hello.cpp
# Кросс-компиляция C-кода
zig cc -target aarch64-linux-gnu hello.c
zig cc -target x86_64-windows-gnu hello.c Тестирование Тестирование встроено в язык и тулчейн. Тестовые блоки являются частью исходного кода, а не отдельным фреймворком.
Тестовые блоки const std = @import("std" );
const expect = std.testing.expect;
fn add (a: i32 , b: i32 ) i32 {
return a + b;
}
test "addition works" {
const result = add (2 , 3 );
try expect (result == 5 );
}
test "string equality" {
const input = "Hello, World!" ;
try std.testing.expectEqualStrings ("Hello" , input[0 .. 5 ]);
}
test "error conditions" {
const result = validate ("" );
try std.testing.expectError (error .EmptyInput, result);
}
test "no memory leaks" {
const allocator = std.testing.allocator; // ловит утечки
var list: std.ArrayListUnmanaged (u8 ) = .{};
defer list.deinit (allocator);
try list.append (allocator, 42 );
try expect (list.items.len == 1 );
}
fn validate (input: []const u8 ) ! void {
if (input.len == 0 ) return error .EmptyInput;
} Утилиты std.testing // Точное равенство
try std.testing.expectEqual (@as(u32 , 42 ), computeValue ());
// Строки (с diff при ошибке)
try std.testing.expectEqualStrings ("expected" , actual);
// Слайсы
try std.testing.expectEqualSlices (u8 , expected_bytes, actual_bytes);
// Приближённое сравнение float
try std.testing.expectApproxEqAbs (@as(f64 , 3.14 ), computePi (), 0.01 );
// Проверка формата
try std.testing.expectFmt ("hello 42" , "{s} {}" , .{ "hello" , @as(u32 , 42 ) });Запуск тестов # Все тесты в файле
zig test src/main.zig
# Через build system
zig build test
# Фильтр по имени
zig test src/main.zig --test-filter "string"
# С подробным выводом
zig test src/main.zig --verbose Async I/O (0.16-dev, экспериментально) История Версия Статус async Pre-0.11 языковые async/await на stackless coroutines 0.11 (2023) async/await удалены, идёт редизайн 0.14–0.15 нет async/await, используйте std.Thread 0.16-dev новый async I/O framework, fibers
Новый дизайн (0.16) // std.Io — интерфейс, передаётся как параметр (как аллокатор)
// Две реализации:
// std.Io.Threaded — синхронные операции с потоками
// std.Io.Evented — event loop (io_uring / kqueue), fibers
// Функция принимает Io-параметр — работает с обеими реализациями
fn fetchData (io: std.Io, url: []const u8 ) ! []u8 {
_ = url;
_ = io;
// ...
}
// Ключевое различие:
// async — разделение вызова и возврата (infallible)
// concurrent — параллельное выполнение операций Дизайн “Writergate” в 0.15 является подготовкой к async: явные буферы и flush позволят переключаться между синхронным и асинхронным I/O без изменения пользовательского кода.
Паттерны RAII через defer/errdefer fn openAndProcess (allocator: std.mem.Allocator, path: []const u8 ) ! Result {
const file = try std.fs.cwd ().openFile (path, .{});
defer file.close ();
const data = try allocator.alloc (u8 , 4096 );
errdefer allocator.free (data); // только при ошибке
const result = try parse (data, file);
return result; // data передаётся вызывающему
} Sentinel values вместо null-указателей // Вместо NULL-указателя — optional
fn findUser (id: u64 ) ?* User {
// ...
return null ; // явно, а не NULL-crash
} Comptime interfaces (duck typing в compile-time) fn serialize (writer: anytype , value: anytype ) ! void {
const T = @TypeOf(value);
if (@hasDecl(T, "serialize" )) {
try value.serialize (writer);
} else {
try writer.print ("{any}" , .{value});
}
} Builder pattern через struct init const ServerConfig = struct {
host: []const u8 = "0.0.0.0" ,
port: u16 = 8080 ,
max_connections: u32 = 1024 ,
read_timeout_ms: u64 = 30_000 ,
};
fn startServer (config: ServerConfig) ! void {
_ = config;
// ...
}
// Использование — лаконично через defaults
try startServer (.{}); // все defaults
try startServer (.{ .port = 3000 }); // только порт
try startServer (.{ .host = "127.0.0.1" , .port = 9090 });Comptime dispatch для полиморфизма fn Animal (comptime T: type ) type {
return struct {
inner: T,
pub fn speak (self: @This()) []const u8 {
return self.inner.speak ();
}
};
}
const Dog = struct {
pub fn speak (_: Dog) []const u8 { return "Woof!" ; }
};
const Cat = struct {
pub fn speak (_: Cat) []const u8 { return "Meow!" ; }
};
const my_dog = Animal (Dog){ .inner = .{} };
const sound = my_dog.speak (); // "Woof!" — без vtable, без heap
_ = sound; Branch hints (0.15) // Заменяет старый @setCold()
fn handleRareCase () void {
@branchHint(.cold);
// редко выполняемый код
}
fn processItem (x: u32 ) void {
if (x > threshold) {
@branchHint(.likely);
// горячий путь
} else {
@branchHint(.unlikely);
// холодный путь
}
} Ключевые изменения по версиям 0.14.0 (март 2025) Labeled switch : continue :sw для state machines (13% ускорение tokenizer)@splat расширен на массивыFile system watching : zig build --watchIncremental compilation : -fincremental251 контрибьютор, 3467 коммитов 0.15.x (август–октябрь 2025), ломающий релиз Компилятор :
x86 self-hosted backend по умолчанию для debug (~5x быстрее LLVM) aarch64 backend активно развивается Writergate (I/O overhaul) :
Явные буферы для readers/writers Обязательный .flush() перед выходом std.io.getStdOut() больше не работаетСтандартная библиотека :
ArrayList → ArrayListUnmanaged (аллокатор в каждый вызов)usingnamespace удалёнТеги типов в нижнем регистре (.@"struct" вместо .Struct) @branchHint(.cold) вместо @setCold()std.rand → std.Randomstd.TailQueue → std.DoublyLinkedListstd.zig.CrossTarget → std.Target.QueryBuild system :
b.createModule() + .root_moduleb.addLibrary(.{ .linkage = .static }) вместо addStaticLibrary()0.16.0-dev (ожидается 2026) std.Io интерфейс для всего I/Ostd.Io.Threaded и std.Io.EventedFibers / stackful coroutines io_uring (Linux) / kqueue (macOS) backends Частые проблемы Забыли flush stdout // ПРОБЛЕМА: вывод не появляется
var buf: [4096 ]u8 = undefined ;
var writer = std.fs.File.stdout ().writer (& buf);
try writer.interface.print ("Hello \n " , .{});
// программа завершается — вывод потерян
// РЕШЕНИЕ: всегда flush перед выходом
try writer.interface.flush ();Утечка памяти: забыли defer // ПРОБЛЕМА: утечка
fn bad () ! []u8 {
const allocator = std.heap.page_allocator;
const buf = try allocator.alloc (u8 , 1024 );
try riskyOperation (buf); // если ошибка — buf утёк
return buf;
}
// РЕШЕНИЕ: errdefer
fn good () ! []u8 {
const allocator = std.heap.page_allocator;
const buf = try allocator.alloc (u8 , 1024 );
errdefer allocator.free (buf);
try riskyOperation (buf);
return buf;
} Dangling pointer из слайса // ПРОБЛЕМА: слайс указывает на стековый массив, который уже уничтожен
fn bad () []const u8 {
var buf: [64 ]u8 = undefined ;
const result = std.fmt.bufPrint (& buf, "hello {}" , .{42 }) catch unreachable ;
return result; // dangling pointer!
}
// РЕШЕНИЕ: аллоцировать или копировать
fn good (allocator: std.mem.Allocator) ! []u8 {
return try std.fmt.allocPrint (allocator, "hello {}" , .{42 });
} Capture по значению в цикле // ПРОБЛЕМА: capture в for — копия, не ссылка
var items = [_]u32 { 1 , 2 , 3 };
for (items) | item| {
_ = item + 1 ; // item — копия, items не изменился
}
// РЕШЕНИЕ: capture по указателю
for (& items) |* item| {
item.* += 1 ; // мутирует оригинал
} Integer overflow в release // ПРОБЛЕМА: в Debug overflow = паника, в Release = undefined behavior
const a: u8 = 255 ;
const b = a + 1 ; // Debug: паника. ReleaseFast: UB (0 или что угодно)
// РЕШЕНИЕ: явные wrapping/saturating операции
const c = a +% 1 ; // wrapping: 0
const d = a +| 1 ; // saturating: 255
_ = b;
_ = c;
_ = d; comptime vs runtime // ПРОБЛЕМА: runtime-значение в comptime-контексте
fn bad (slice: []const u8 ) void {
// const arr: [slice.len]u8 = undefined; // ОШИБКА: slice.len не comptime
_ = slice;
}
// РЕШЕНИЕ: использовать слайсы или передать размер как comptime
fn good (comptime N: usize ) [N]u8 {
return @splat(0 );
} Сравнение слайсов // ПРОБЛЕМА: == не работает для слайсов
const a: []const u8 = "hello" ;
const b: []const u8 = "hello" ;
// if (a == b) {} // ОШИБКА компиляции: operator == not allowed for []const u8
// РЕШЕНИЕ: std.mem.eql
if (std.mem.eql (u8 , a, b)) {
// равны
} usingnamespace удалён (0.15) // ПРОБЛЕМА: старый код с usingnamespace не компилируется
// usingnamespace @import("other.zig"); // ошибка в 0.15
// РЕШЕНИЕ: явный импорт
const other = @import("other.zig" );
// используйте other.Foo, other.bar() ArrayList → ArrayListUnmanaged (0.15) // ПРОБЛЕМА: старый код с ArrayList не компилируется
// var list = std.ArrayList(i32).init(allocator); // ошибка в 0.15
// РЕШЕНИЕ: ArrayListUnmanaged, аллокатор в каждый вызов
var list: std.ArrayListUnmanaged (i32 ) = .{};
defer list.deinit (allocator);
try list.append (allocator, 42 );// ПРОБЛЕМА: старые теги в верхнем регистре
// switch (@typeInfo(T)) { .Struct => ... } // ошибка в 0.15
// РЕШЕНИЕ: нижний регистр, ключевые слова через @""
switch (@typeInfo(T)) {
.@"struct" => {}, // struct — ключевое слово
.int => {}, // int — не ключевое слово
.float => {},
else => {},
} Задачи для прокачки до Middle Задачи расположены от простых к сложным. Каждая прокачивает конкретные навыки.
Все задачи прикладные: после выполнения у тебя будет работающий инструмент или библиотека.
Уровень 1. Основы языка 1.1. CLI-утилита: подсчёт строк (wc -l)
Задача 1.1: CLI-утилита подсчёта строкНавыки : файловый I/O, аргументы командной строки, error handling, форматированный вывод
Требования :
Принимает список файлов через argv Для каждого файла выводит: количество строк, слов, байт, имя файла Поддерживает stdin (если файлов нет — читает из stdin) Итоговая строка с суммой (если файлов > 1) Корректно обрабатывает ошибки: файл не найден, нет прав, бинарный файл Пример :
$ ./zwc src/main.zig src/lib.zig
142 387 4521 src/main.zig
89 201 2103 src/lib.zig
231 588 6624 total 1.2. Парсер CSV
Задача 1.2: Парсер CSVНавыки : строковая обработка, аллокаторы, слайсы, итераторы, тестирование
Требования :
Парсит CSV с произвольным разделителем (по умолчанию запятая) Поддерживает кавычки: "field with, comma" → одно поле Поддерживает escape кавычек: "he said ""hello""" → he said "hello" Итератор по строкам, итератор по полям в строке Работает с любым аллокатором Тесты на edge cases: пустые поля, переводы строк в кавычках, trailing CRLF API :
var parser = csv.parse (allocator, data, .{ .delimiter = ';' });
defer parser.deinit ();
while (parser.next ()) | row| {
for (row.fields ()) | field| { .. . }
} 1.3. Ring buffer (кольцевой буфер)
Задача 1.3: Ring bufferНавыки : generics через comptime, указатели, управление памятью, тестирование
Требования :
Generic : RingBuffer(comptime T: type, comptime capacity: usize)Операции : push, pop, peek, isFull, isEmpty, lenФиксированный размер (без аллокаций в runtime) Поддержка итерации (for-compatible iterator) Overwrite-режим : при push в полный буфер — затирает старейший элементТесты :
push/pop последовательности wrap-around (переход через конец массива) overwrite-режим итерация в правильном порядке пустой и полный буфер 1.4. Генератор случайных паролей
Задача 1.4: Генератор паролейНавыки : std.Random, работа со строками, компоновка буферов, CLI
Требования :
Настраиваемая длина (по умолчанию 16) Флаги: --uppercase, --lowercase, --digits, --symbols (по умолчанию все) Флаг --count N (генерировать N паролей) Флаг --exclude "chars" (исключить символы) Криптографически стойкий: std.crypto.random Проверка: сгенерированный пароль содержит хотя бы по одному символу из каждой включённой категории Пример :
$ ./zpasswd --length 24 --count 5 --exclude "0OlI1" Уровень 2. Структуры данных и алгоритмы 2.1. HashMap с открытой адресацией
Задача 2.1: HashMap с открытой адресациейНавыки : хеширование, управление памятью, generics, resize, тестирование под нагрузкой
Требования :
Generic : HashMap(comptime K: type, comptime V: type)Robin Hood hashing (при коллизии — вставка ближе к идеальной позиции)Автоматический resize при load factor > 0.75 Операции : put, get, remove, contains, count, iteratorПринимает аллокатор как параметр Поддержка пользовательских hash/eql функций Бенчмарк: сравнить производительность с std.HashMapUnmanaged Тесты :
100 000 вставок/поисков коллизии (намеренно плохая хеш-функция) удаление без tombstone-утечек корректный resize 2.2. Arena allocator с диагностикой
Задача 2.2: Arena allocator с диагностикойНавыки : аллокаторы (std.mem.Allocator interface), страницы памяти, introspection
Требования :
Реализует std.mem.Allocator interface Аллоцирует блоками (chunks) из parent allocator reset() — освободить всё без возврата памяти ОС (переиспользование)deinit() — вернуть всё parent allocatorДиагностика : total_allocated, peak_usage, num_allocations, num_chunksОпциональный лимит (maxBytes) — возвращает error.OutOfMemory при превышении Alignment-aware : корректно выравнивает все аллокацииТесты :
Использование с std.ArrayListUnmanaged и std.HashMapUnmanaged Проверка что reset реально переиспользует память Проверка alignment для разных типов Утечки через std.testing.allocator как parent 2.3. Thread pool
Задача 2.3: Thread poolНавыки : многопоточность, синхронизация, Mutex, Condition, очереди задач
Требования :
Пул из N потоков (настраиваемо) submit(fn, args) -> Future — отправить задачуFuture.wait() -> результат — дождаться завершенияGraceful shutdown : finish() — дождаться всех задач и остановить потокиОчередь задач на основе lock-free или mutex-protected ring buffer Обработка паник в worker-потоках (не убивать весь пул) Тесты :
1000 задач на пул из 4 потоков Все результаты корректны Нет data races (проверить через GPA + ThreadSanitizer) Graceful shutdown не теряет задачи Уровень 3. Сетевое программирование 3.1. HTTP/1.1 сервер
Задача 3.1: HTTP/1.1 серверНавыки : TCP сокеты, парсинг протокола, потоки, буферизованный I/O, keep-alive
Требования :
Парсинг HTTP/1.1 запросов (метод, путь, заголовки, тело) Поддержка методов: GET, POST, PUT, DELETE, HEAD Content-Length и Transfer-Encoding: chunked (чтение тела)Keep-alive (Connection: keep-alive / close)Роутинг : регистрация обработчиков по пути и методуСтатические файлы : отдача из директории с правильным Content-TypeMiddleware : logging (метод, путь, статус, время обработки)Многопоточность : по потоку на соединение (или через thread pool из задачи 2.3)API :
var server = try HttpServer.init (allocator, .{ .port = 8080 });
server.get ("/" , handleIndex);
server.get ("/api/users" , handleUsers);
server.post ("/api/users" , createUser);
server.static ("/static" , "./public" );
try server.listen ();3.2. TCP чат (клиент + сервер)
Задача 3.2: TCP чат (клиент + сервер)Навыки : сетевое программирование, многопользовательский ввод/вывод, протокол сообщений
Требования (сервер) :
Принимает множество TCP-соединений Каждое соединение = участник чата Broadcast : сообщение от одного уходит всем остальнымКоманды : /nick <name>, /list, /quit, /whisper <nick> <msg>Уведомления: “User X joined”, “User X left” Обработка обрыва соединения (не ронять сервер) Требования (клиент) :
Подключается к серверу по IP:port Неблокирующий ввод: одновременно читает stdin и сокет Отображение сообщений в реальном времени Протокол (бинарный или текстовый — на выбор):
Бинарный: [2 bytes: msg_type][4 bytes: length][payload] Текстовый: MSG nick message\n, CMD /nick newname\n 3.3. DNS resolver
Задача 3.3: DNS resolverНавыки : бинарные протоколы, UDP сокеты, bit-level парсинг, сериализация
Требования :
Формирование DNS-запроса (Query) по RFC 1035 Отправка через UDP на 8.8.8.8:53 (или настраиваемый сервер) Парсинг ответа: заголовок, question section, answer section Поддержка типов записей: A , AAAA , CNAME , MX , TXT , NS Обработка сжатия имён (pointer compression в DNS) Таймаут на ответ (3 секунды) CLI: ./zdns example.com A Пример :
$ ./zdns google.com A
google.com IN A 142.250.74.206 ( TTL: 300)
$ ./zdns google.com MX
google.com IN MX 10 smtp.google.com ( TTL: 600) Уровень 4. Системное программирование 4.1. Key-Value хранилище с persistence
Задача 4.1: Key-Value хранилище с persistenceНавыки : файловый I/O, сериализация, буферизация, crash recovery, бенчмаркинг
Требования :
API : put(key, value), get(key) -> ?value, delete(key), scan(prefix) -> iteratorКлючи и значения: []const u8 (произвольные байты) Write-Ahead Log (WAL) : каждая операция сначала пишется в логПериодический snapshot : дамп всех данных на диск Recovery : при запуске — прочитать snapshot + replay WALfsync после записи в WAL (гарантия durability)Бенчмарк: ops/sec для put и get (цель: >100K ops/sec на SSD) Ограничение по памяти: если данные не влезают — вытеснение (LRU ) Формат WAL : [4B: crc32][1B: op_type][4B: key_len][4B: val_len][key][value]
4.2. Memory-mapped файловый поиск (grep)
Задача 4.2: Memory-mapped файловый поиск (grep)Навыки : mmap, SIMD-подсказки через @Vector, многопоточность, рекурсивный обход FS
Требования :
Поиск строки/regex в файлах Memory-mapped I/O (std.posix.mmap или std.fs)Рекурсивный обход директорий Параллельный поиск : по потоку на файл (через thread pool)Вывод: файл:строка:содержимое (как grep -n) Фильтрация по расширению: --include "*.zig" --exclude "zig-cache" Подсветка совпадения в выводе (ANSI escape codes) Бенчмарк: сравнить с системным grep на большом дереве исходников Бонус : Boyer-Moore или SIMD-ускоренный поиск подстроки
4.3. Логгер с ротацией
Задача 4.3: Логгер с ротациейНавыки : потокобезопасность, файловый I/O, форматирование, конфигурация
Требования :
Уровни : debug, info, warn, err, fatalПотокобезопасный (множество потоков пишут одновременно)Вывод : stderr, файл, или обаФормат : [2025-03-15T10:23:45.123Z] [INFO] [thread:3] messageРотация по размеру : при превышении max_size → переименовать в .1, .2, …Ротация по количеству : хранить не более N файловStructured logging : log.info("request", .{ .method = "GET", .path = "/api", .ms = 42 })Реализует std.log interface (scoped logger) Lazy formatting : аргументы форматируются только если уровень >= текущегоAPI :
const log = try Logger.init (allocator, .{
.level = .info,
.file_path = "/var/log/myapp.log" ,
.max_size = 10 * 1024 * 1024 , // 10MB
.max_files = 5 ,
});
defer log.deinit ();
log.info ("server started on port {}" , .{8080 }); Уровень 5. Comptime и метапрограммирование 5.1. Comptime ORM / query builder
Задача 5.1: Comptime ORM / query builderНавыки : comptime type generation, @typeInfo, inline for, строковые операции в comptime
Требования :
Определение модели через обычную struct: const User = struct {
id: u64 ,
name: []const u8 ,
email: []const u8 ,
age: ?u32 ,
created_at: i64 ,
}; API :
const db = try Database.init (allocator, "sqlite3.db" );
const users = db.table (User);
// Генерирует: SELECT id, name, email, age, created_at FROM users WHERE age > ?
const results = try users.where (.{ .age = .{ .gt = 18 } }).select ();
// Генерирует: INSERT INTO users (name, email, age) VALUES (?, ?, ?)
try users.insert (.{ .name = "Alice" , .email = "a@b.com" , .age = 25 , .created_at = now });5.2. Компилируемый маршрутизатор (router)
Задача 5.2: Компилируемый маршрутизатор (router)Навыки : comptime строковой парсинг, type generation, tagged unions
Требования :
Маршруты определяются в comptime: const router = comptime Router.init (.{
.{ .GET, "/" , handleIndex },
.{ .GET, "/users/:id" , handleUser },
.{ .POST, "/users" , createUser },
.{ .GET, "/users/:id/posts/:pid" , handlePost },
.{ .GET, "/files/*path" , serveFile },
}); fn handleUser (params: struct { id: u64 }, req: * Request) ! Response { .. . }
fn handlePost (params: struct { id: u64 , pid: u64 }, req: * Request) ! Response { .. . }5.3. Сериализатор/десериализатор (MessagePack или CBOR)
Задача 5.3: Сериализатор/десериализатор (MessagePack или CBOR)Навыки : comptime type reflection, бинарные форматы, generics, рекурсивные типы
Требования :
Автоматическая сериализация любой Zig struct в MessagePack (или CBOR ) Автоматическая десериализация обратно в struct Поддержка типов: int, float, bool, []const u8, optional, slice, struct, enum, tagged union Вложенные структуры (рекурсивно) Comptime-проверка : если тип не поддерживается — ошибка компиляции с понятным сообщениемStreaming API : сериализация в writer, десериализация из reader (без промежуточных аллокаций)API :
const msgpack = @import("msgpack" );
const Point = struct { x: f32 , y: f32 };
const Line = struct { start: Point, end: Point, color: ?[]const u8 };
var buf: [1024 ]u8 = undefined ;
const encoded = try msgpack.encode (& buf, Line{
.start = .{ .x = 0 , .y = 0 },
.end = .{ .x = 10 , .y = 20 },
.color = "red" ,
});
const decoded = try msgpack.decode (Line, encoded);Уровень 6. Интеграция и production-ready проекты 6.1. SQLite wrapper с comptime маппингом
Задача 6.1: SQLite wrapper с comptime маппингомНавыки : C interop, build.zig, error mapping, comptime, resource management
Требования :
Линковка SQLite через build.zig (addCSourceFiles или linkSystemLibrary) Обёртка над C API: open, close, prepare, step, finalize Zig-идиоматичные ошибки (sqlite3 error codes → Zig error set) Comptime mapping : результат запроса → Zig structPrepared statements с bind-параметрамиТранзакции : begin/commit/rollback с defer-паттерномПул соединений (опционально) API :
const db = try Sqlite.open (allocator, "app.db" );
defer db.close ();
try db.exec ("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)" );
// Компилятор проверяет: количество ? = количество аргументов
try db.exec ("INSERT INTO users (name, age) VALUES (?, ?)" , .{ "Alice" , 30 });
// Результат автоматически маппится в struct
const User = struct { id: i64 , name: []const u8 , age: i32 };
var rows = try db.query (User, "SELECT id, name, age FROM users WHERE age > ?" , .{18 });
defer rows.deinit ();
while (rows.next ()) | user| {
std.debug.print ("{s} ({}) \n " , .{ user.name, user.age });
} 6.2. CLI framework
Задача 6.2: CLI frameworkНавыки : comptime, build system, модульная архитектура, man pages, completion
Требования :
Определение команд и флагов через comptime struct: const cli = Cli.init (.{
.name = "mytool" ,
.version = "1.0.0" ,
.description = "My awesome tool" ,
.commands = & .{
.{ .name = "init" , .handler = cmdInit, .args = struct {
path: []const u8 = "." ,
@"--template": []const u8 = "default" ,
@"--force": bool = false ,
}},
.{ .name = "build" , .handler = cmdBuild, .args = struct {
@"--release": bool = false ,
@"--target": ?[]const u8 = null ,
@"--jobs": u32 = 4 ,
}},
},
}); fn cmdInit (args: struct { path: []const u8 , .. . }) ! void { .. . }Цветной вывод ошибок (ANSI) Подкоманды: mytool remote add <name> <url> 6.3. Сборщик статического сайта
Задача 6.3: Сборщик статического сайтаНавыки : файловый I/O, рекурсивный обход, шаблонизация, Markdown→HTML, build.zig интеграция
Требования :
Читает .md файлы из content/ Парсит Markdown → HTML (headings, paragraphs, code blocks, links, images, lists) Front matter (YAML-like метаданные в начале файла):---
title : My Post
date : 2025-03-15
tags : zig, programming
--- Шаблоны : layouts/ с placeholder-ами {{ content }}, {{ title }}, {{ date }}Генерирует output/ со статическими HTML-файлами Копирует static/ (CSS, JS, изображения) без изменений Индексная страница со списком постов (сортировка по дате) RSS/Atom feed генерацияИнкрементальная сборка : пересобирать только изменённые файлы (по mtime)Встроенный dev-сервер : localhost:3000 с live-reload (через WebSocket или polling) CLI :
$ ./zssg build # полная сборка
$ ./zssg serve # dev-сервер с live-reload
$ ./zssg new "Post Title" # создать новый .md с front matter Карта прогресса
Карта прогрессаУровень 1 — Основы (1-2 недели на задачу)
1.1 wc clone – файлы, ошибки, CLI args, форматирование 1.2 CSV parser – строки, аллокаторы, итераторы, тесты 1.3 Ring buffer – generics, comptime, указатели 1.4 Password gen – std.crypto.random, CLI, строки Уровень 2 — Структуры данных (1-2 недели)
2.1 HashMap – хеширование, resize, generics, бенчмарки 2.2 Arena allocator – Allocator interface, страницы, alignment 2.3 Thread pool – Mutex, Condition, очереди, потоки Уровень 3 — Сеть (2-3 недели)
3.1 HTTP сервер – TCP, парсинг протокола, роутинг, потоки 3.2 TCP чат – многопользовательский I/O, протокол сообщений 3.3 DNS resolver – бинарный протокол, UDP, bit-level парсинг Уровень 4 — Системное (2-3 недели)
4.1 KV store – WAL, persistence, crash recovery, fsync 4.2 grep clone – mmap, параллельный поиск, рекурсия FS 4.3 Logger – потокобезопасность, ротация, structured logging Уровень 5 — Comptime (2-3 недели)
5.1 Query builder – @typeInfo, comptime codegen, SQL 5.2 Router – comptime trie, type generation, tagged unions 5.3 Serializer – reflection, бинарный формат, streaming Уровень 6 — Production (3-4 недели)
6.1 SQLite wrapper – C interop, build.zig, error mapping 6.2 CLI framework – comptime, модульность, автогенерация 6.3 Static site gen – всё вместе: I/O, парсинг, шаблоны, сеть Итого : ~3-4 месяца интенсивной работы -> уверенный Middle Zig разработчик
Чеклист навыков Middle Zig
Чеклист навыков Middle ZigПосле выполнения всех задач вы должны уметь:
Язык :
Свободно работать с error unions, optional, tagged unions Понимать ownership: кто владеет памятью, defer vs errdefer Писать generic-код через comptime Использовать @typeInfo, @field, inline for для метапрограммирования Понимать разницу comptime vs runtime Память :
Выбирать правильный аллокатор для задачи Реализовывать собственный аллокатор (Allocator interface) Избегать утечек, dangling pointers, use-after-free Использовать arena для request-scoped работы Профилировать потребление памяти I/O и сеть :
Работать с файлами, директориями, mmap Реализовывать TCP клиент/сервер Парсить бинарные и текстовые протоколы Буферизованный I/O (0.15 Writergate) Конкурентность :
Создавать и управлять потоками Синхронизация: Mutex, Condition, атомарные операции Реализовывать thread pool Избегать data races Tooling :
Писать build.zig для сложных проектов Подключать C-библиотеки (linkLibC, addCSourceFiles) Управлять зависимостями (build.zig.zon) Кросс-компиляция Писать тесты с std.testing Паттерны :
RAII через defer/errdefer Builder pattern через struct defaults Comptime interfaces (duck typing) Iterator pattern Allocator injection