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.
Проверьте себя
1. Чем catch отличается от try?
AНичем
Bcatch обрабатывает ошибку на месте, try пробрасывает её наверх
Ccatch только для опционалов
Dtry работает в рантайме, а catch при компиляции
2. Чему эквивалентно выражение try x?
Ax orelse null
Bx catch |err| return err
Cx catch -1
Dswitch (x)