Zig: язык системного программирования

Концепции

Zig — компилируемый язык без 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\nContent-Length: 5\r\n\r\nHello", .{});
        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, экспериментально)

История

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 --watch
  • Incremental compilation: -fincremental
  • 251 контрибьютор, 3467 коммитов

0.15.x (август–октябрь 2025), ломающий релиз

Компилятор:

  • x86 self-hosted backend по умолчанию для debug (~5x быстрее LLVM)
  • aarch64 backend активно развивается

Writergate (I/O overhaul):

  • Явные буферы для readers/writers
  • Обязательный .flush() перед выходом
  • std.io.getStdOut() больше не работает

Стандартная библиотека:

  • ArrayListArrayListUnmanaged (аллокатор в каждый вызов)
  • usingnamespace удалён
  • Теги типов в нижнем регистре (.@"struct" вместо .Struct)
  • @branchHint(.cold) вместо @setCold()
  • std.randstd.Random
  • std.TailQueuestd.DoublyLinkedList
  • std.zig.CrossTargetstd.Target.Query

Build system:

  • b.createModule() + .root_module
  • b.addLibrary(.{ .linkage = .static }) вместо addStaticLibrary()

0.16.0-dev (ожидается 2026)

  • std.Io интерфейс для всего I/O
  • std.Io.Threaded и std.Io.Evented
  • Fibers / 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);

Type reflection tags (0.15)

// ПРОБЛЕМА: старые теги в верхнем регистре
// switch (@typeInfo(T)) { .Struct => ... }   // ошибка в 0.15

// РЕШЕНИЕ: нижний регистр, ключевые слова через @""
switch (@typeInfo(T)) {
    .@"struct" => {},    // struct — ключевое слово
    .int => {},          // int — не ключевое слово
    .float => {},
    else => {},
}

Задачи для прокачки до Middle

Задачи расположены от простых к сложным. Каждая прокачивает конкретные навыки. Все задачи прикладные: после выполнения у тебя будет работающий инструмент или библиотека.


Уровень 1. Основы языка

1.1. CLI-утилита: подсчёт строк (wc -l)

Навыки: файловый 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

Навыки: строковая обработка, аллокаторы, слайсы, итераторы, тестирование

Требования:
- Парсит 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 (кольцевой буфер)

Навыки: 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. Генератор случайных паролей

Навыки: 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 с открытой адресацией

Навыки: хеширование, управление памятью, 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 с диагностикой

Навыки: аллокаторы (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

Навыки: многопоточность, синхронизация, 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 сервер

Навыки: 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-Type
- Middleware: 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 чат (клиент + сервер)

Навыки: сетевое программирование, многопользовательский ввод/вывод, протокол сообщений

Требования:
Сервер:
- Принимает множество 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

Навыки: бинарные протоколы, 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

Навыки: файловый 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 WAL
- fsync после записи в 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)

Навыки: 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. Логгер с ротацией

Навыки: потокобезопасность, файловый 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

Навыки: comptime type generation, @typeInfo, inline for, строковые операции в comptime

Требования:
- Определение модели через обычную struct:
    const User = struct {
        id: u64,
        name: []const u8,
        email: []const u8,
        age: ?u32,
        created_at: i64,
    };

- Comptime-генерация:
    - CREATE TABLE SQL из struct (типы Zig → SQL типы)
    - INSERT SQL с placeholder-ами
    - SELECT с маппингом результата обратно в struct
    - WHERE builder с типобезопасными условиями

- Валидация в compile-time:
    - Несуществующее поле → ошибка компиляции
    - Неправильный тип в условии → ошибка компиляции

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)

Навыки: 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 },
    });

- Comptime-генерация:
    - Trie или radix tree строится в compile-time
    - Для каждого маршрута — struct с типизированными параметрами
    - Параметры :id автоматически парсятся в нужный тип
    - *path — wildcard (остаток пути)

- В runtime: только lookup по дереву, без аллокаций
- Обработчик получает типизированные параметры:
    fn handleUser(params: struct { id: u64 }, req: *Request) !Response { ... }
    fn handlePost(params: struct { id: u64, pid: u64 }, req: *Request) !Response { ... }

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 маппингом

Навыки: 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 struct
- Prepared 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

Навыки: 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,
            }},
        },
    });

- Автогенерация:
    - --help для каждой команды (из struct field names и defaults)
    - bash/zsh/fish completion скрипт
    - Валидация типов аргументов (число, путь, enum)

- Обработчик получает типизированную struct:
    fn cmdInit(args: struct { path: []const u8, ... }) !void { ... }

- Цветной вывод ошибок (ANSI)
- Подкоманды: mytool remote add <name> <url>

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

После выполнения всех задач ты должен уметь:

[Язык]
 ✓ Свободно работать с 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