Note: None of this has been implemented.
An exception occurs whenever a program tries to do something and fails. Sometimes, there is no graceful way to recover from certain exceptions. For example, maybe the program:
- Needs more heap or stack memory and it is all used up.
- Encounters a math error, such as an overflow or divide by zero.
- Tries to access an out-of-bounds memory location.
When there is no obvious way to recover from an exception, the program "panics", shutting down as quickly and gracefully as possible. Part of the shutdown process involves reporting on the nature of the failure so the logic problem that failed to prevent this problem can be fixed.
In many cases, however, it is possible and desirable to recover from an exception. This page describes how.
Recoverable exceptions are always directly communicated from one function back to the function that called it. Instead of returning the expected value, it throws an exception value.
fn factorial(mut n i32) i32 throws Bool: if n <= 0: throw false mut result = 1 while n: result *= n-- result
This function specifies throws as part of its signature to indicate that if it cannot successfully return an integer value, it will throw a boolean exception instead. The first line does exactly that: If the parameter is a negative number, it throws an exception value of false.
Now that we have a function that can throw an exception, let's show different ways to handle it.
Give exception a default value
Use the || operator to establish a default value in the event of a thrown exception.
fn calc(n i32) i32: factorial(n) || 0
If factorial throws an exception, it is treated as if it had returned 0 instead. The value to the right of the operator must be a plain value, and its type must match that of the success value on the left.
Panic on exception
Use the ? operator following any call to an exception-throwing function.
fn calc(n i32) i32: factorial(n)? + 10
This checks whether the function call succeeds and returns a value. If so, it adds 10 to that result and returns it. Since this function gives no clue on how to handle an exception, the default behavior on a thrown exception will be that the program panics and shuts down.
Another approach involves re-throwing the exception to the next caller up the call stack.
fn calc(n i32) i32 throws Bool: factorial(n)? + 10
Even though this function still does not specify an exception handler, its signature shows that it also throws exceptions. As a result, if the call to factorial throws an exception, that exception will be re-thrown to the function which called calc. Importantly, there must be a type match between the exception value received and the one the function re-throws.
catch exception handlers
If we want some other behavior than panic or re-throw, one or more exception handlers must be specified in the form of a catch blocks. This block appears at some point after the try, but need not be immediately afterwards.
fn calc(n i32) i32: factorial(n)? + 10 catch: 0
Should the call to factorial throw an exception, the catch block handler will be run. In this case, it returns the number 0. In effect, we have completely recovered from the thrown exception, as this calc itself throws no exception.
Alternatively, we could handle it by throwing an exception of a different type or value.
fn calc(n i32) i32 throws i32: factorial(n)? + 10 catch: throw 0
If we need more information about the exception, a variable name (and optionally a type) may also be specified on the catch statement. This binds to the exception value, so that it may be interrogated:
fn calc(n i32) i32 throws i32: factorial(n)? + 10 catch err: if err: panic else: throw 0
This handler will trigger a panic if the thrown exception value is false. Otherwise, it will throw a new exception value of 0.
Recovering from a thrown exception may be possible and the right thing to do in certain circumstances. However, sometimes the right response to a bad condition is to intentionally shut the program down. As shown above, one way to do this is using the panic statement.
assert and requires
The assert statement triggers a panic when its condition evaluates to false.
assert idx < bound
assert is only performed in test builds. To get the equivalent guarantee in production as well:
requires idx < bound
Where we want a function to preserve some invariants on entry and exit from the function, the requires and ensures clauses may be added to a function signature. These provide a visibly strengthened contract that governs use of the function. If these conditions fail, a panic is triggered.
fn factorial(mut n i32) i32 requires n > 0 ensures result > 0: mut result = 1 while n: result *= n-- result
This function expects that it will get a positive, non-zero number and return the same. Any other behavior is a bug which we want to catch quickly in testing and fix.