Дженерики через comptime-типы
Реализуем обобщённый код через типы-параметры — без шаблонов и без отдельного синтаксиса.
Дженерик в Zig — это обычная функция или структура, принимающая тип как comptime-параметр; никакого специального синтаксиса шаблонов нет — обобщённость выражается тем же языком.
В C++ дженерики — это шаблоны с отдельным синтаксисом template<...>. В Java и C# — параметры типов в угловых скобках. В Zig обобщённый код не требует никаких новых конструкций: раз типы — это значения, функция просто принимает тип comptime-параметром и возвращает... другой тип.
Обобщённая функция
const std = @import("std");
// T — тип, известный при компиляции; max работает для любого числового типа
fn max(comptime T: type, a: T, b: T) T {
return if (a > b) a else b;
}
pub fn main() void {
std.debug.print("{d} {d}\n", .{ max(i32, 3, 7), max(f64, 2.5, 1.1) });
}
Вывод:
7 2.5
Функция max принимает первым параметром comptime T: type — сам тип. Остальные параметры и возврат используют T. При каждом вызове компилятор подставляет конкретный тип и порождает специализированную версию. Это монооморфизация, как у шаблонов C++, но выраженная обычным кодом.
Обобщённая структура — функция, возвращающая тип
// функция, которая ВОЗВРАЩАЕТ новый тип — это и есть generic-контейнер
fn Stack(comptime T: type) type {
return struct {
items: []T,
len: usize,
fn top(self: @This()) T {
return self.items[self.len - 1];
}
};
}
// использование: Stack(i32) — это конкретный тип
const IntStack = Stack(i32);
Вот ключевой приём: обобщённый контейнер — это функция, принимающая тип и возвращающая тип (struct). Вызов Stack(i32) исполняется при компиляции и порождает конкретную структуру стека для i32. @This() внутри ссылается на текущий тип-структуру. Именно так в стандартной библиотеке устроены ArrayList и HashMap.
Как работает под капотом
Поскольку Stack — comptime-функция, компилятор исполняет её при каждом уникальном аргументе-типе и кеширует результат. Stack(i32) и Stack(f64) дадут два разных типа, но Stack(i32) дважды — один и тот же. Никакого стирания типов, как в Java: каждая специализация — это отдельный, полностью типизированный код, оптимизируемый компилятором.
Частые ошибки
Первая — искать синтаксис <T>: его в Zig нет, тип передают обычным аргументом. Вторая — забыть comptime у параметра-типа: тип обязан быть известен при компиляции. Третья — путать Stack (функция) и Stack(i32) (результат — конкретный тип): экземпляры создают именно от второго.
Итог
- Дженерики в Zig — обычные функции с
comptime T: type, без особого синтаксиса. - Обобщённый контейнер — это функция, принимающая тип и возвращающая
struct. @This()ссылается на текущий тип-структуру внутри неё.- Каждая специализация — отдельный типизированный код; стирания типов нет.