Exceptions
Overview
Fantom uses exceptions as the standard mechanism for error handling. An exception is a normal object which subclasses from Err. Exceptions are thrown or raised to indicate an error condition. When an exception is raised, normal program execution is disrupted and the call stack is unwound until an exception handler is found to handle the exception.
Fantom does not use Java styled checked exceptions where a method must declare the exceptions it throws. In our experience checked exceptions are syntax salt which don't scale to large projects. For example in Java the real exception is often wrapped dozens of times to make the compiler happy as independent subsystems are integrated.
Err Class
All exceptions in Fantom subclass from Err. Many APIs declare
their own Err classes for modeling specific error conditions. The
type hierarchy is the primary mechanism used to classify exceptions
and is used to match exceptions to exception handlers.
All Errs also have a message and a cause. Message is a human
readable Str to describe the error condition. Cause is another
Err instance which is the root cause of the exception. Cause is
often null if there is no root cause.
Fantom convention requires all exception classes end with Err and declare
a constructor called make which takes at least a Str message as
the first parameter and an Err cause as the last parameter. Typically
both of these parameters have a default argument of null.
When an exception is thrown, the runtime captures the call stack of
and stores it with the Err instance. You can dump the call stack
of an exception using the trace method:
err.trace // dumps to Env.cur.out
err.trace(log) // dumps to log output stream
Throw Statement
The throw statement is used to raise an exception via the
following syntax:
// syntax
throw <expr>
// example
throw IndexErr("index $i > $len")
The expression used with the throw keyword must be evaluate
to a sys::Err type. When the throw statement is executed,
the exception is raised and program execution unwinds itself
to the first matching exception handler.
Try-Catch Statement
Fantom uses the try-catch-finally syntax of Java and C#
for exception handling:
try
{
<block>
}
catch (<type> <identifier>)
{
<block>
}
catch
{
<block>
}
finally
{
<block>
}
A list of catch blocks is used to specify exception handlers based
on exception type. A catch block for a given type will catch all
raised exceptions of that type plus its subclasses. If an exception
is raised inside a catch block with no matching catch blocks, then
the exception continues to unwind the call stack (although if
a finally block is specified it will be executed).
The blocks of code in a try can be either a {} block or a
single statement. The identifier of each catch block is used to
access the exception caught - this variable is scoped within the
catch block itself.
You can have as many catch blocks as you like, however the type
of a catch block cannot repeat a type used previously. For example:
// this is legal
try {...}
catch (CastErr e) {...}
catch (NullErr e) {...}
catch (Err e) {...}
// this is illegal
try {...}
catch (Err e) {...}
catch (NullErr e) {...}
In the first block of code we first declare a catch handler for
CastErr and NullErr which will catch any exception of those
types. We catch Err last which will catch anything else which is
not a CastErr or NullErr. However the second block of code will
not compile because the NullErr catch comes after the Err catch
block (which includes NullErrs).
If you don't need to access the exception caught, you can also use a catch all block to catch all exceptions using the following shorthand syntax:
try
{
return doSomethingDangerous
}
catch
{
return null
}
Finally Blocks
We use a finally block when we want to execute some code which
runs regardless of how or if an exception is handled. A finally
block always executes when a try block exits. Finally is often
used to ensure that resources are cleaned up properly no matter
what exceptions might occur. For example the following code
guarantees that the input stream is closed no matter what might
go wrong inside the try block:
void load(InStream in)
{
try
{
// read input stream
}
finally
{
in.close
}
}
Finally blocks have similar restrictions to C#. You cannot exit
a finally block directly using a return, break, or continue
statement.