Шаг 5. Ошибки
Зачем#
В OxL нет исключений. Исключение — невидимый путь выхода из функции: глядя на сигнатуру, невозможно узнать, что она
может «бросить». Вместо этого ошибка — обычное значение, а возможные ошибки перечислены в типе результата:
-> User | NotFound | Timeout. Тип с @error нельзя проигнорировать: если результат функции, которая может вернуть ошибку,
просто отброшен, компилятор остановит сборку.
Отдельно существуют паники — они означают баг (деление на ноль, выход за границы, нарушение контракта) и завершают текущую задачу; их не ловят и не обрабатывают как обычную ошибку (§6.6).
Пример#
@error data NotFound { id: i64 } // @error помечает тип как ошибку
@error data Invalid { why: str }
fn find(id: i64) -> str | NotFound { // результат — str или NotFound
if id == 1 { return "ann" }
fail NotFound{ id } // fail возвращает ошибку
}
fn parseId(s: str) -> i64 | Invalid {
match s.parseInt() {
i64 n => n
ParseError => fail Invalid{ why: "not a number: {s}" }
}
}
fn lookup(s: str) -> str | NotFound | Invalid { // сигнатура перечисляет все ошибки
let id = parseId(s)? // ? пробрасывает ошибку вызывающему
find(id)?
}
fn main() io {
for s in ["1", "2", "x"] {
match lookup(s) { // match разбирает результат по типу
str name => print("found {name}")
NotFound e => print("no user {e.id}")
Invalid e => print("bad input: {e.why}")
}
}
let r = await find(2) // цепочка: -> следующий шаг, !> обработчик
-> it.len()
!> NotFound => 0
print("{r}")
}found ann no user 2 bad input: not a number: x 0
Разбор#
@error data NotFound { id: i64 }— обычная запись, помеченная как ошибка. Ошибки могут быть иenum.-> str | NotFound— «строка илиNotFound».fail NotFound{ id }возвращает ошибку из функции; тип ошибки обязан входить в сигнатуру (иначе —fail-type).parseId(s)?— оператор?: если результат — ошибка, функция сразу возвращает её; иначе значение достаётся наружу. Ошибки обеих вызванных функций поэтому перечислены в сигнатуреlookup.matchразбирает результат по типу. Если пропустить ошибку, получитсяnon-exhaustive.->— цепочка: следующий шаг получает результат вit.!> NotFound => 0— обработчик: ошибка заменяется значением, и в типе результата она исчезает. Цепочки особенно полезны при сетевых вызовах и подробно разобраны в §8.4.- Если
mainвозвращает ошибки (fn main() -> () | Usage | IoError io), необработанная ошибка завершает процесс с кодом 1.
Ещё несколько способов работать с результатом:
| Запись | Смысл |
|---|---|
r? | вернуть ошибку из текущей функции, иначе достать значение |
r! | достать значение; при ошибке — паника (допустимо в main, тестах, скриптах) |
r is NotFound | проверка типа, сужает r в ветке if |
_ = r | явно отбросить результат (редко и осознанно) |
x ?? d | значение по умолчанию для T? |
Типичная ошибка: проигнорированный результат#
@error data Declined { reason: str }
fn charge(sum: i64) -> i64 | Declined => sum
fn main() {
charge(10) // результат с @error нельзя проигнорировать
}unhandled-error
Это самая частая диагностика новичка: unhandled-error. Исправьте её одним из способов:
charge(10)? (передать выше), match, charge(10)! (если ошибка тут невозможна), либо _ = charge(10) (отбросить осознанно).
Что запомнить#
- Ошибки — значения в типе результата, а не исключения.
?— пробросить,match— обработать,!>— заменить в цепочке.- Сеть добавляет
NetErrorк результату любого rpc (§6.5) — это тоже обычная ошибка, и её видно в сигнатуре.
Дальше: коллекции и итераторы.