OxL Справочник OxL
РусскийEnglish

Шаг 5. Ошибки

Зачем#

В OxL нет исключений. Исключение — невидимый путь выхода из функции: глядя на сигнатуру, невозможно узнать, что она может «бросить». Вместо этого ошибка — обычное значение, а возможные ошибки перечислены в типе результата: -> User | NotFound | Timeout. Тип с @error нельзя проигнорировать: если результат функции, которая может вернуть ошибку, просто отброшен, компилятор остановит сборку.

Отдельно существуют паники — они означают баг (деление на ноль, выход за границы, нарушение контракта) и завершают текущую задачу; их не ловят и не обрабатывают как обычную ошибку (§6.6).

Пример#

oxl✓ проверяется в CI
@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?

Типичная ошибка: проигнорированный результат#

oxl✓ проверяется в CI
@error data Declined { reason: str }

fn charge(sum: i64) -> i64 | Declined => sum

fn main() {
  charge(10)  // результат с @error нельзя проигнорировать
}
error
unhandled-error

Это самая частая диагностика новичка: unhandled-error. Исправьте её одним из способов: charge(10)? (передать выше), match, charge(10)! (если ошибка тут невозможна), либо _ = charge(10) (отбросить осознанно).

Что запомнить#

  • Ошибки — значения в типе результата, а не исключения.
  • ? — пробросить, match — обработать, !> — заменить в цепочке.
  • Сеть добавляет NetError к результату любого rpc (§6.5) — это тоже обычная ошибка, и её видно в сигнатуре.

Дальше: коллекции и итераторы.

См. также

OxL 1.0.0-beta · Версия документации 1.0.0-beta© OxwaldLink · сгенерировано из Стандарта, исходников std и руководства