Skip to content
Playground

Errors ​

error is a shorthand declaration for typed errors that flow as effects.

Declaration ​

yulang
pub error io_err:
    not_found path
    denied path
    invalid_path path
    failed (path, str)

This generates several pieces at once:

  • A pub enum io_err whose variants are not_found path, denied path, invalid_path path, and failed (path, str).
  • A pub act io_err whose operations share the variant names and return never.
  • An impl Throw io_err with type throws = '[io_err] and our e.throw that performs the matching effect operation.
  • An impl Display io_err with a default text rendering (overridable with a hand-written impl).
  • io_err::wrap in the companion module, which closes the error effect into a result value.
  • An up helper in the companion module when the declaration contains from entries. It lifts the linked narrower errors into the declared error type.

Constructors and operations share names ​

Each variant name is both a data constructor and an effect operation. The surrounding context picks the right one.

yulang
my err: io_err = io_err::not_found path    // built as a value
io_err::not_found path                       // raised as an effect

Raising with fail ​

fail is a prelude prefix operator defined as a transparent wrapper around e.throw:

yulang
pub prefix(fail) = \e -> e.throw

Use it to surface a constructed error value into the effect row.

yulang
my missing path = fail (io_err::not_found path)

Calling missing performs io_err::not_found; fail makes that error visible in the effect row.

Catching by name ​

catch arms handle errors by naming each operation directly.

yulang
my read_text_or_label path = catch read_text path:
    io_err::not_found _, _ -> "(missing)"
    io_err::denied _, _ -> "(denied)"
    value -> value

Yulang's error story is built on catching by name. There is no type-erased catch-all and no runtime dispatch over arbitrary Display instances; Yulang intentionally does not provide an anyhow-style boundary. Each error keeps its concrete type in the effect row, so the origin and the handler of every error are visible from types alone.

wrap: closing into a value ​

yulang
my read_text_safe path =
    my wrapped = io_err::wrap: read_text path
    case wrapped:
        result::ok text -> text
        result::err err -> err.show

E::wrap catches the matching error effect produced by its thunk argument and returns result _ E. When E has from entries, wrap also catches the linked narrower errors and wraps them through the generated conversions.

from aggregation ​

The following excerpt assumes that a parser module defines parse_err and parse_json:

yulang
pub error app_err:
    file from io_err
    parse from parse_err

This generates:

  • variants app_err::file io_err and app_err::parse parse_err.
  • generated conversions from io_err and parse_err to app_err.
  • an extended app_err::wrap that also catches io_err and parse_err.
  • app_err::up, a handler that turns the narrower errors into app_err.
yulang
my read_and_parse path =
    app_err::up:
        my text = read_text path                // [io_err]
        parse_json text                          // [parse_err]
    // the block as a whole has effect [app_err]

See also Casts for the underlying conversion machinery and Effects for the general catch and effect-row story.

Yulang