Nil and Exception Handling
Punctuation: <Prefix> <Call | Index | Lookup | TryOperator | NilCoalesce>*
TryOperator: '?' <Punctuation>?;
NilCoalesce: '??' <Punctuation>;
Try: 'try' <Expression> ('catch' <MatchBlock | SingleCatch | ArrowCatch>)?;
SingleCatch: <GuardedPattern> <Block>;
ArrowCatch: '=>' <Expression>;
Throw: 'throw' <Expression>?;
Muse embraces exception handling for propagating errors and aims to make handling errors straightfoward when desired.
When an exception is thrown, Muse unwinds the execution stack to the nearest
catch
handler. If the catch handler matches the exception thrown, the
exception handler will be executed. If not, Muse will continue to unwind the
stack until a matching handler is found or execution returns to the embedding
application. In Rust, the result will be Err(_)
.
Any value can be used as an exception, and handling specific functions is done using pattern matching. Consider this example:
fn first(n) {
try {
second(n)
} catch :bad_arg {
second(1)
}
};
fn second(n) {
try {
100 / n
} catch :divided_by_zero {
throw :bad_arg
}
};
first(0)
When the above example is executed, second is invoked with 0
, which causes a
divide by zero exception. The exception occurs within the try
block, and is
caught by the catch :divide_by_zero
block.
The catch block proceeds to throw the symbol :bad_arg
as a new exception.
first()
is executing second()
within a try block that catches that value,
and calls second(1)
instead. This will succeed, and return a result of 100
.
While this is a nonsensical example, it highlights the basic way that exception handling and pattern matching can be combined when handling errors.
Catch any error
Catching any error can be done in one of two ways: using an identifier binding or using an arrow catch block. In the next example, both functions are identical:
fn identifier_binding() {
try {
1 / 0
} catch err {
err
}
}
fn arrow_catch() {
try {
1 / 0
} catch => {
# when not provided a name, `it` is bound to the thrown exception.
it
}
}
Matching multiple errors
To catch multiple errors raised by inside of a try block, a match block can be used:
fn checked_area(width,height) {
if width == 0 {
throw :invalid_width
} else if height == 0 {
throw :invalid_height
}
width * height
};
try {
checked_area(0, 100)
} catch {
:invalid_width => {}
:invalid_height => {}
}
Converting any exception to nil
A try
block without a catch
block will return nil
if an exception is
raised. Similarly, the try operator (?
) can be used to convert any exception
raised to nil
. These examples produce identical code:
try {
1 / 0
};
(1 / 0)?;