Exceptions
Exceptions allow a program to transfer control from the point of error to a designated exception handler, separating error-handling logic from regular program flow.
Throwing exceptions
Exceptions are thrown using the throw
function. Example:
grabOption[T](option: Option[T]): T {
| Some(v) => v
| None => throw(GrabException())
}
In this example, if the argument is Some(v)
, then v
is returned. Otherwise, a GrabException
is thrown.
Any type declared with the data
or newtype
keyword can be thrown as an exception, since the type will have an instance for the HasAnyTag
trait.
Catching exceptions
When an exception is thrown, it propagates up the call chain to the nearest try
, where it can be caught:
try {
grabOption(None)
} catch {| GrabException, error =>
Log.trace("A GrabException occurred")
error.rethrow()
}
The first argument passed to catch
is the thrown value, and the second parameter contains the stack trace.
The rethrow()
method throws the exception again without altering the stack trace.
It is used when the exception is caught where it can't be fully handled.
If the exception reaches the top of the call stack, the exception value and the stack trace will be printed.
Catching any exception
It is also posssible to catch any exception, regardless of its type, using the catchAny
method:
try {
let result = fragileOperation()
Some(result)
} catchAny {error =>
None
}
Cleaning up
Cleanup often needs to happen whether or not an exception is thrown.
This is what the finally
method guarantees:
let fileHandle = path.readHandle()
try {
process(fileHandle)
} finally {
fileHandle.close()
}
If the program is terminated abnormally (force closed, power is lost, etc.) there is no guarantee that finally
will get called.
In most environments, the operating system will occasionally terminate the program abnormally, so programs should be designed such that they can recover from abnormal termination.
Catching multiple exceptions
The try
function returns a value of type Try[T]
that is defined as follows:
data Try[T] {
Success(value: T)
Failure(error: Error)
}
The catch
, catchAny
and finally
methods are defined on this type. However, since they don't return a Try[T]
value, they can't be chained.
However, the alternative tryCatch
, tryCatchAny
and tryFinally
methods do return a Try[T]
value, and can thus be chained:
try {
doSomething()
} tryCatch {| GrabException, error =>
reportSpecificError()
} tryCatchAny {error =>
reportGeneralError()
} finally {
performCleanup()
}
The last method in the chain here is finally
. If it was tryFinally
, a value of type Try[T]
would be returned.