comptime: код на этапе компиляции

Открываем главную суперсилу Zig — выполнение обычного кода во время компиляции.

comptime — ключевое слово, помечающее код, который выполняется на этапе компиляции, а не в рантайме; в Zig это единый механизм для констант, дженериков и метапрограммирования.

Если у Zig есть одна определяющая черта — это comptime. Идея проста, но мощна: тот же самый код Zig, который вы пишете для рантайма, может выполняться и во время компиляции. Не отдельный язык макросов, не шаблоны со своим синтаксисом — обычный Zig, исполненный компилятором.

Вычисления на этапе компиляции

fn factorial(n: u64) u64 {
    var result: u64 = 1;
    var i: u64 = 2;
    while (i <= n) : (i += 1) result *= i;
    return result;
}

// comptime заставляет вычислить значение при компиляции
const fact10 = comptime factorial(10); // 3628800, посчитано компилятором

Ключевое слово comptime перед выражением требует вычислить его при компиляции. factorial(10) исполнится компилятором, и в бинарник попадёт уже готовое число 3628800 — в рантайме ничего не считается. При этом factorial — обычная функция, её же можно вызвать и в рантайме.

Типы — это значения

// тип можно положить в const, как обычное значение
const MyInt = i32;
const x: MyInt = 5;

// и даже выбрать тип на этапе компиляции
const T = if (true) i32 else f64; // T == i32

Здесь скрыта революционная идея: в Zig типы — это значения типа type, доступные на этапе компиляции. Их можно присваивать константам, передавать в функции, выбирать через if. Именно это превращает обычные функции в генераторы дженериков, о чём — следующий урок.

comptime-параметры функций

fn repeat(comptime n: usize, value: u8) [n]u8 {
    // n известно при компиляции, поэтому можно вернуть массив [n]u8
    return [_]u8{value} ** n;
}

const trio = repeat(3, 65); // [3]u8{ 65, 65, 65 }

Параметр, помеченный comptime, обязан быть известен при компиляции. Это позволяет использовать его там, где нужны константы: например, как длину возвращаемого массива. Оператор ** повторяет массив n раз — тоже comptime-операция.

Как работает под капотом

Компилятор Zig содержит интерпретатор подмножества языка. Когда он встречает comptime-вычисление, он исполняет код прямо во время компиляции и подставляет результат. Поскольку это тот же язык, нет разрыва между «языком макросов» и «языком программы» — отсюда читаемость. Ограничение: comptime-код не может делать то, что требует рантайма (например, обращаться к вводу-выводу).

Частые ошибки

Первая — ждать, что comptime выполнит ввод-вывод или системные вызовы: ему доступны только чистые вычисления. Вторая — забыть пометить параметр comptime, когда от него зависит тип возврата, и получить ошибку «значение неизвестно при компиляции». Третья — путать comptime с препроцессором: это полноценный Zig, а не текстовая подстановка.

Итог

  • comptime исполняет обычный код Zig на этапе компиляции — без отдельного языка макросов.
  • В Zig типы — это значения типа type, доступные при компиляции.
  • Параметр comptime n можно использовать там, где нужны константы (например, длина массива).
  • comptime-код чист: ввод-вывод и системные вызовы ему недоступны.
Проверьте себя
1. Что делает comptime перед выражением factorial(10)?
AОткладывает вычисление на рантайм
BЗаставляет вычислить значение на этапе компиляции и вложить результат в бинарник
CСоздаёт макрос
DПомечает функцию как небезопасную
2. Чем в Zig являются типы с точки зрения comptime?
AСтроками
BЗначениями типа type, доступными на этапе компиляции
CТолько именами без значения
DУказателями