# Error handling


Xo has two kinds of failure, and the language keeps them apart:

- an **error** is something the caller can reasonably handle (bad input, a
  missing file). It is a value in the return type after `!`.
- a **fault** is a bug (a broken `requires`, an index out of range). It
  stops the task, and only an isolation boundary turns it into a value.

## Layered errors with `from`

Each layer has its own error enum. A `from` variant lets `?` convert a lower
layer's error into the upper one.

`errors-chain/main.xo`

```xo
enum ParseErr {
  Empty
  NotANumber(text: Str)
}

fn (e: ParseErr) display() -> Str {
  match e {
    Empty => "empty input"
    NotANumber(text) => "not a number: ${text}"
  }
}

enum RangeErr {
  TooSmall(n: Int, min: Int)
  TooBig(n: Int, max: Int)
}

fn (e: RangeErr) display() -> Str {
  match e {
    TooSmall(n, min) => "${n} is below ${min}"
    TooBig(n, max) => "${n} is above ${max}"
  }
}

/// The error of `port`: either layer's error converts into it, because of `from`.
enum PortErr {
  Parse(from ParseErr)
  Range(from RangeErr)
}

fn (e: PortErr) display() -> Str {
  match e {
    Parse(p) => "bad port text: ${p.display()}"
    Range(r) => "bad port number: ${r.display()}"
  }
}

fn parse(text: Str) -> Int ! ParseErr {
  if text.trim() == "" {
    return Err(ParseErr.Empty)
  }
  Int.parse(text.trim()).or_err(ParseErr.NotANumber(text))?
}

fn check_range(n: Int) -> Int ! RangeErr {
  if n < 1024 {
    return Err(RangeErr.TooSmall(n: n, min: 1024))
  }
  if n > 65535 {
    return Err(RangeErr.TooBig(n: n, max: 65535))
  }
  n
}

/// Each `?` returns early with the error converted to `PortErr`.
fn port(text: Str) -> Int ! PortErr {
  let n = parse(text)?
  check_range(n)?
}

fn main(os: Os) {
  for text in ["8080", "", "http", "80", "70000"] {
    match port(text) {
      Ok(p) => os.stdio.println("port ${p}")
      Err(e) => os.stdio.println("error: ${e.display()}")
    }
  }
}

test "the error says which layer failed" {
  match port("80") {
    Ok(_) => fault("expected an error")
    Err(Range(TooSmall(n, _))) => expect(n) == 80
    Err(e) => fault("unexpected: ${e.display()}")
  }
}
```


Output:

```text
port 8080
error: bad port text: empty input
error: bad port text: not a number: http
error: bad port number: 80 is below 1024
error: bad port number: 70000 is above 65535
```



- `port` returns `Int ! PortErr`. `parse(text)?` yields a `ParseErr`, which
  the `Parse(from ParseErr)` variant wraps; `check_range(n)?` does the same
  for `RangeErr`.
- The caller can still tell which layer failed: the test matches
  `Err(Range(TooSmall(n, _)))`.
- Variants with two fields are built with named arguments.

Ignoring a result is not allowed to pass silently:

```xo
fn parse_port(s: Str) -> Int ! Error {
  Int.parse(s).or_err("not a number")?
}

fn main(os: Os) {
  parse_port("8080")
}
```

[Try it in the playground](/play/#example=mistakes/ignored)
{.play-link}

`xo check` says:

```text
main.xo:6:3: error XO0201: result of `parse_port` is not handled
  note: `parse_port` can fail with `Error`; propagate with `?`, match on it, or discard it with `.ignore_err("reason")` (core 2.6)
  fix: discard with a reason
1 module(s), 1 error(s), 0 warning(s)
```


## Handling an error without ceremony

`errors-handling/main.xo`

```xo
fn lookup(env: Map[Str, Str], key: Str) -> Str? {
  env.get(key)
}

/// `??` takes the value, or the right side when there is none.
fn port_or_default(env: Map[Str, Str]) -> Int {
  let text = lookup(env, "PORT") ?? return 8080
  Int.parse(text) ?? 8080
}

/// Turn a missing value into an error with `or_err`, and pass errors up with `?`.
fn strict_port(env: Map[Str, Str]) -> Int ! Error {
  let text = lookup(env, "PORT").or_err("PORT is not set")?
  Int.parse(text).or_err("PORT is not a number: ${text}")?
}

/// `r ?? e => body` handles the error and gives the fallback.
fn port_logged(out: Stdio, env: Map[Str, Str]) -> Int uses stdio {
  strict_port(env) ?? e => {
    out.println("falling back: ${e.display()}")
    8080
  }
}

fn main(os: Os) {
  let cases: List[Map[Str, Str]] = [{"PORT": "9000"}, {"PORT": "x"}, {}]
  for env in cases {
    os.stdio.println("default: ${port_or_default(env)}")
    os.stdio.println("logged: ${port_logged(os.stdio, env)}")
    match strict_port(env) {
      Ok(p) => os.stdio.println("strict: ${p}")
      Err(e) => os.stdio.println("strict failed: ${e.display()}")
    }
    if let Some(p) = lookup(env, "PORT") {
      os.stdio.println("PORT is set to ${p}")
    }
  }
}

test "the fallback only applies on an error" {
  expect(port_or_default({"PORT": "9000"})) == 9000
  expect(port_or_default({"PORT": "x"})) == 8080
  expect(port_or_default({})) == 8080
}
```


Output:

```text
default: 9000
logged: 9000
strict: 9000
PORT is set to 9000
default: 8080
falling back: PORT is not a number: x
logged: 8080
strict failed: PORT is not a number: x
PORT is set to x
default: 8080
falling back: PORT is not set
logged: 8080
strict failed: PORT is not set
```



- `a ?? b` gives the value, or `b` when there is none. With a `Result` the
  right side may bind the error: `r ?? e => { ... }`.
- `?? return x` leaves the function early when the value is missing.
- `or_err` turns an `Option` into a `Result`; `?` passes the error up.
- `if let Some(p) = ...` handles just the case you care about.

## Faults are not errors

`errors-faults/main.xo`

```xo
use std/task

/// A bad index is a bug in the caller, not an error to handle: it is a fault.
fn nth(xs: List[Int], i: Int) -> Int
  requires i >= 0 && i < xs.len()
{
  xs[i]
}

/// Bad input from outside is an error: the caller decides what to do.
fn nth_checked(xs: List[Int], i: Int) -> Int ! Error {
  if i < 0 || i >= xs.len() {
    return Err("index ${i} out of range for ${xs.len()} items")
  }
  xs[i]
}

fn main(os: Os) {
  let xs = [10, 20, 30]
  os.stdio.println("nth: ${nth(xs, 1)}")
  match nth_checked(xs, 7) {
    Ok(v) => os.stdio.println("got ${v}")
    Err(e) => os.stdio.println("error: ${e.display()}")
  }
  // `task.isolate` runs code in its own scope and turns a fault into a value
  match task.isolate(fn() -> Int ! Error { Ok(nth(xs, 7)) }) {
    Ok(v) => os.stdio.println("got ${v}")
    Err(Faulted(f)) => os.stdio.println("fault caught: ${f.msg}")
    Err(Failed(e)) => os.stdio.println("error: ${e.display()}")
  }
}
```


Output:

```text
nth: 20
error: index 7 out of range for 3 items
fault caught: requires failed: i >= 0 && i < xs.len()
```



- `nth` states its precondition with `requires`. Breaking it is the caller's
  bug, so it is a fault, not an error value.
- `nth_checked` takes input that may legitimately be wrong, so it returns an
  error.
- `task.isolate` runs a function in its own scope and returns `Faulted` when
  it faults. Use it at a service boundary (one request), not to hide bugs.

## What to take away

- Input from outside is an error; a broken promise inside the program is a
  fault.
- Give each layer its own error enum and connect them with `from`.
- Prefer `??` for fallbacks and `match` when each case needs its own action.

Back to the [book overview](/docs/book/).

