Error unions и try
Знакомимся с обработкой ошибок Zig — без исключений и без скрытой раскрутки стека.
Error union
!T— тип значения, которое либо успешно (типаT), либо является ошибкой; функция явно объявляет, что может вернуть ошибку, и вызывающий обязан её обработать.
В большинстве языков ошибки — это исключения, которые незаметно раскручивают стек. Zig отвергает этот подход: исключений нет вовсе. Вместо них ошибка — это обычное значение, а возможность ошибки видна прямо в типе возврата функции через восклицательный знак.
Объявление функции, которая может ошибиться
const DivError = error{ DivisionByZero };
fn divide(a: i32, b: i32) DivError!i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
Тип возврата DivError!i32 означает: «либо i32, либо ошибка из набора DivError». Ошибку возвращают как обычное значение: return error.DivisionByZero. Набор ошибок объявляют через error{...} — это перечисление возможных провалов.
Оператор try — проброс ошибки наверх
fn compute() DivError!i32 {
const x = try divide(10, 2); // если ошибка — вернуть её из compute
return x + 1;
}
Оператор try — это сокращение: «попробуй; если получилось — дай значение, если ошибка — немедленно верни её из текущей функции». Он заменяет громоздкую проверку if (err) return err из C. Поток управления при этом полностью явный: try видно в коде на каждом месте, где ошибка может «выпрыгнуть».
Вывод ошибки в наборе
// !i32 без явного набора — Zig сам выведет множество ошибок
fn parse(s: []const u8) !i32 {
if (s.len == 0) return error.Empty;
return 0;
}
Если написать просто !i32, Zig сам выведет, какие ошибки может вернуть функция, проанализировав её тело. Это удобно: не нужно вручную перечислять набор, но при желании его можно зафиксировать явно для документации.
Как работает под капотом
Error union физически — это значение плюс компактный код ошибки (целое число). Никакой раскрутки стека, никаких таблиц исключений. Возврат ошибки — это обычный return, такой же дешёвый, как возврат числа. Поэтому в Zig обработка ошибок не вредит производительности и предсказуема — это критично для системного кода.
Частые ошибки
Первая — проигнорировать возможную ошибку: компилятор не даст просто присвоить результат !T переменной типа T без обработки. Вторая — путать try и catch: try пробрасывает ошибку наверх, а catch (следующий урок) обрабатывает на месте. Третья — забыть, что ошибки в Zig — это значения, а не объекты с данными: код ошибки несёт только своё имя.
Итог
- В Zig нет исключений; ошибка — это значение в типе
!T. - Набор ошибок объявляют через
error{...}, возвращают какerror.Name. try exprпробрасывает ошибку из текущей функции, если она возникла.- Тип
!Tбез явного набора заставляет компилятор вывести множество ошибок сам.