Skip to content
Menu
Contents

Errors

Chapter 7 of 9

When something can fail: Ok, Err and the ? shortcut.

When something can fail

Some things can fail. Turning "abc" into a number, for example. In Bert a function that can fail gives back Ok or Err instead of crashing: Ok with the answer, or Err with what went wrong. You have already met Ok and Err in the Lists and Variants chapters.

Int.fromString("42")     # => Ok(42)
Int.fromString("abc")    # => Err(InvalidInt)

A value built with Ok or Err is called a Result. You will see that word in signatures and in the docs.

Because it is a variant, match makes you deal with both:

show = (result) -> match result {
  Ok(n) -> String.concat("Got ", Int.toString(n))
  Err(InvalidInt) -> "Not a number"
}

Mistakes the compiler catches

Some mistakes never get as far as running. If you add a number to a piece of text, Bert stops before the program runs:

main.bert
total = 1 + "two"
Output
─── type.unification_failure ──────────────────────────

Expected `Int`, but got `String`.

main.bert:1:13
1 | total = 1 + "two"
                ^^^^^

Read it from the top. The first line names the kind of error. Next comes what went wrong: + wanted a whole number (Int), but got text (String). Last comes where: file main.bert, line 1, column 13, with ^^^^^ under the part that does not fit.

Some mistakes still stop the program

Not everything gives back an Err. Dividing by zero with / stops the program on the spot:

7 / 0
Output
wasm trap: integer divide by zero

When the number you divide by might be zero, use Int.divChecked. It gives back a value instead:

Int.divChecked(7, 2)    # => Ok(3)
Int.divChecked(7, 0)    # => Err(DivByZero)

The ? shortcut

Matching every step gets long. Put a ? after a call that can fail. On Ok, you get the value. On Err, the function stops right there and passes the error up:

addAges = (a, b) -> {
  x = Int.fromString(a)?
  y = Int.fromString(b)?
  Ok(x + y)
}

addAges("1", "2")     # => Ok(3)
addAges("1", "zz")    # => Err(InvalidInt)

The happy path reads top to bottom. A function that uses ? must itself return an Ok or Err, which is why it ends with Ok(x + y).

? in a chain

A ? works after each stage of a |> chain too. On Ok, you get the value. On Err, the function stops right there and passes the error up:

first = (line) -> {
  parts = String.split(line, ",")
  a = parts
    |> List.head()?
    |> Int.fromString()?
  Ok(a)
}

first("7,8")    # => Ok(7)
first("x,8")    # => Err(InvalidInt)

Using a Result at the top of your program

Sooner or later a match has to decide what to do with an Err. In a program, that is usually where you print a friendly message:

main.bert
external export {
  _start : () -> Unit
  _start = () -> match Int.fromString("42") {
    Ok(n) -> WasiCli.print(String.concat("Twice that is ", Int.toString(n * 2)))
    Err(_) -> WasiCli.print("That is not a number.")
  }
}
Output
Twice that is 84

Change "42" to "abc" and it prints That is not a number. instead.

When a fallback value is all you need, Result.withDefault gives you the value inside an Ok, or the default you pass for an Err:

Result.withDefault(Int.fromString("42"), 0)     # => 42
Result.withDefault(Int.fromString("abc"), 0)    # => 0