catch и обработка на месте
Учимся обрабатывать ошибку прямо в точке вызова через catch.
catch — оператор, который перехватывает ошибку из
!Tи позволяет обработать её на месте: подставить значение по умолчанию, выполнить блок или разобрать конкретный код ошибки.
Если try пробрасывает ошибку наверх, то catch — её зеркальная пара: он обрабатывает ошибку прямо здесь и сейчас. Это нужно, когда вызывающий код знает, как разумно отреагировать на сбой, и не хочет передавать его дальше.
Значение по умолчанию
const DivError = error{ DivisionByZero };
fn divide(a: i32, b: i32) DivError!i32 {
if (b == 0) return error.DivisionByZero;
return @divTrunc(a, b);
}
pub fn main() void {
const result = divide(10, 0) catch -1; // при ошибке берём -1
const std = @import("std");
std.debug.print("result={d}\n", .{result});
}
Вывод:
result=-1
Запись divide(10, 0) catch -1 читается так: «вычисли; если ошибка — подставь -1». Это самый компактный способ дать запасное значение, аналог orelse для опционалов.
Блок обработки с захватом ошибки
const value = divide(10, 0) catch |err| {
std.debug.print("произошла ошибка: {s}\n", .{@errorName(err)});
return; // или вернуть запасное значение
};
Конструкция catch |err| захватывает сам код ошибки в переменную err. Внутри блока можно его залогировать, преобразовать или выполнить откат. Функция @errorName возвращает имя ошибки строкой — полезно для диагностики.
Разбор конкретных ошибок через switch
const data = readConfig() catch |err| switch (err) {
error.FileNotFound => useDefaults(),
error.PermissionDenied => return err, // пробросить дальше
else => return err,
};
Поскольку коды ошибок — это перечисление, их естественно разбирать через switch. Для одних ошибок вы восстанавливаетесь, для других — пробрасываете наверх. Компилятор проследит, чтобы вы обработали все варианты или добавили else.
Как работает под капотом
catch разворачивается компилятором в проверку «это ошибка?» и переход на блок обработки — обычный условный код, без исключений. Именно поэтому try x эквивалентно x catch |err| return err: try — это просто частный случай catch, который всегда пробрасывает ошибку. Понимание этого делает обе конструкции прозрачными.
Частые ошибки
Первая — подставлять через catch значение неправильного типа: оно должно совпадать с T в !T. Вторая — глотать ошибку молча через catch {} и терять важную информацию о сбое; логируйте хотя бы имя. Третья — забыть, что switch по ошибке обязан быть исчерпывающим.
Итог
catchобрабатывает ошибку на месте, в отличие от пробрасывающегоtry.expr catch default— компактная подстановка запасного значения.catch |err| { ... }захватывает код ошибки для логирования или отката.try x— это сокращение дляx catch |err| return err.