Exceptions

Throwing exceptions

Crow supports exceptions in a similar way to other languages.

main void() throw "Oh no!" error

throw takes an exception argument. exception is a variant type and error is a variant-member of it.
(For a refresher: Variants)

throw is an expression that can provide any expected type.

main void() xs string[nat] = (1, "one"), (2, "two") info log (xs[1] ?? throw "Nothing for 1" error) info log (xs[3] ?? throw "Nothing for 3" error)

"Assert" and "Forbid"

In most cases an exception is not thrown unconditionally. assert and forbid are shorthand for conditionally throwing exceptions.
assert throws an exception if its condition is false, and forbid throws if its condition is true.

main void() assert 2::nat < 3 forbid 3::nat < 4

By default, these throw error exceptions. A custom exception can be written after a :.

main void() assert 3::nat < 2 : divide-by-0

Like a guard, an assert can take an option condition.
The unwrapped option is available below the assert, otherwise an error is thrown.

main void() xs nat mut[] = 1, assert x ?= xs pop info log "got {x}"

forbid can also take an option condition, and the unwrapped value is available in the exception expression.

main void() xs nat mut[] = 1, forbid x ?= xs pop : "Unexpectedly popped {x}" error

Catching exceptions

You can also catch exceptions.

main void() info log try "{1::nat / 0}" catch divide-by-0 "not a number"

Just like for an if, both branches of the try expression should return the same type, in this case string.

The catch works like an as in a match, where the thing being matched is the exception.
You can have multiple catch handlers for different possible exceptions.
If there is no exception or the exception does not match the expected variant-member, the catch has no effect.

There is a minor issue above: The try wraps both the division and the conversion to a string. Often you want precise control over exactly which expression has its exceptions caught.
To make that more convenient, you can combine try and =:

main void() try x nat = 1 / 0 catch divide-by-0 : error log "Boo" info log "{x}"

Above, exceptions will only be caught in 1 / 0, not in anything below.
Like a guard, this has two branches: A success branch below and an error branch to the right.
It's asymmetric since the error branch is generally shorter and less important.

As with a guard, the : branch is optional and defaults to (). So the below will not log anything.

main void() try x nat = 1 / 0 catch divide-by-0 info log "{x}"

Defining an exception type

Since exception is just a variant, a custom exception is a variant-member.

main void() try for i : 1::nat .. 12 info log "12 / {i} = {12 must-divide-by i}" catch not-divisible x error log "{x}" not-divisible record(numerator nat, denominator nat) exception variant-member show string(a not-divisible) "{a numerator} was not divisible by {a denominator}" must-divide-by nat(a nat, b nat) assert a is-multiple-of b : a not-divisible b a / b

"Finally"

Crow supports a "finally" expression that works like in other languages, but with different syntax.
It is a single line, where the argument to finally runs after the code below it (regardless of whether the below code throws an exception).

main void() info log "start" finally info log "end" info log "middle"

The reason it's done this way is that typically finally is used to clean something up, so it's best to put it right after the thing it cleans up to be sure you don't forget.

import system/stdlib: free, malloc main void() unsafe, (libc, native) extern x nat8 mut* = 1 malloc finally x free *x := 1 y nat8 mut* = 1 malloc # 'y' is freed before 'x'. finally y free *y := 2 info log "{*x + *y}"

Catching all exceptions

There's no syntax for this, but there is a catch-all function. It returns a (t, exception-and-backtrace) result. More details in exception.

You normally don't need to call this as you could just rely on the default exception handler. But this would give you the opportunity to format your own custom exceptions.

main void() unsafe match (() => foo) catch-all as ok x info log "{x}" as error e error log "Caught: {e}" foo nat() bar bar nat() 1 / 0