Error Handling¶
The Chapel language supports throw
, try
, try!
, catch
,
and throws
which are described below. Chapel supports several error
handling modes, including a mode suitable for prototype development and
a less-permissive mode intended for production code.
Implementation Notes.
Additional information about the current implementation of error handling and the strict error handling mode, which is not defined here, is available in the errorHandling technical note
Throwing Errors¶
Errors may be thrown from a function to its callee with a throw
statement. For a function to throw an error, its signature must include
a throws
declaration. The declaration is put after the return type
and before any where
clauses.
The statements following a throw statement in the same block are ignored by the compiler because they cannot be executed.
Only owned
instances of a type inheriting from Error
can be
thrown.
Example (throwing.chpl).
proc canThrow(i: int): int throws { if i < 0 then throw new owned Error(); return i + 1; } proc alwaysThrows():int throws { throw new owned Error(); // never reached return 1; }
Handling Errors¶
There are three ways to handle an error:
Halt with
try!
.Handle the error with
catch
blocks.Propagate the error out of the current function with
throws
.
Halting on error with try!¶
If an error is thrown by a call within the lexical scope of a try!
block or a try!
expression prefix, the program halts.
Example (try-bang.chpl).
proc haltsOnError():int { // the try! next to the throwing call // halts the program if an error occurs. return try! canThrow(0); } proc haltsOnErrorBlock() { try! { canThrow(1); canThrow(0); } }
Handling an error with catch¶
When an error is raised by a call in a try
or try!
block, the
rest of the block is abandoned and control flow is passed to its
catch
clause(s), if any.
Catch clauses¶
A try
or try!
block can have one or more catch
clauses.
A catch
clause can specify the variable that refers to the caught
error within the catch
block. If the variable is given a type, for
example catch e:SomeError
, it is a type filter. The corresponding
catch
clause matches the errors that are of the class
SomeError
or its subclass. If no type filter is present on a catch
clause, or if no variable is present at all, then it is a catchall
clause, which matches all errors.
The type filters are evaluated in the order that the catch
clauses
appear in the program. If a catch
clause’s type filter matches, then
its block is executed to the exclusion of the others. Hence there is no
notion of best match, only a first match.
If the catch
block itself throws an error, it is handled in the same
manner as if that error were thrown by a statement adjacent to the
try
-catch
blocks. Otherwise, after the execution of the
catch
block completes, the program execution proceeds to the next
statement after the try
-catch
blocks.
Example (catching-errors.chpl).
use OS; proc catchingErrors() throws { try { alwaysThrows(0); } catch { writeln("caught an error, unnamed catchall clause"); } try { var x = alwaysThrows(-1); writeln("never reached"); } catch e:FileNotFoundError { writeln("caught an error, FileNotFoundError type filter matched"); } catch e { writeln("caught an error in a named catchall clause"); } }
try! with catch¶
If an error is thrown within a try!
block and none of its catch
clauses, if any, match that error, the program halts.
Example (catching-errors-halt.chpl).
use OS; proc catchingErrorsHalt() { try! { var x = alwaysThrows(-1); writeln("never reached"); } catch e:FileNotFoundError { writeln("caught a file not found error"); } // errors other than FileNotFoundError cause a halt }
Nested try¶
If an error is thrown within a try
block and none of its catch
clauses, if any, match that error, the error is directed to the
enclosing try
block, when present.
Example (nested-try.chpl).
class DemoError : Error { } proc nestedTry() { try { try { alwaysThrows(0); } catch e: DemoError { writeln("caught a DemoError"); } writeln("never reached"); } catch { writeln("caught an Error from inner try"); } }
Propagating an error with throws¶
A function marked throws
can pass along an error thrown by a
function called within it. This can be done in several ways.
After catch clauses¶
Propagation can occur when no matching catch
clause is found for an
error raised in a try
block.
Example (catching-errors-propagate.chpl).
use OS; proc catchingErrorsPropagate() throws { try { var x = alwaysThrows(-1); writeln("never reached"); } catch e:FileNotFoundError { writeln("caught a file not found error"); } // errors other than FileNotFoundError propagate }
catch-less try¶
A logical extension of the above is the case where no catch
blocks
are attached to the try
. Here the try
keyword marks throwing
calls to clarify control flow.
Example (propagates-error.chpl).
proc propagatesError() throws { // control flow changes if an error was thrown; // could be indicated more clearly with try canThrow(0); try canThrow(0); try { canThrow(0); } var x = try canThrow(1); writeln(x); return try canThrow(0); }
try expressions¶
try
and try!
are available as expressions to clarify control
flow at expression granularity. The expression form may not be used with
catch
clauses.
Example (expression-try.chpl).
proc expressionTry(): int throws { var x = try canThrow(1); writeln(x); return try canThrow(0); }
Complete handling¶
For a function to handle errors from its calls without itself throwing,
its try
/catch
must be complete. This may be accomplished in two
ways:
A catchall clause on
try
. This preventstry
from propagating the error out of the function as described above.Example (warns-on-error.chpl).
proc warnsOnError(i: int): int { try { alwaysThrows(i); } catch e { writeln("Warning: caught a error ", e); } }
try!
instead oftry
. This will halt the program if no matchingcatch
clause is found, instead of propagating.Example (halts-on-error.chpl).
class DemoError : Error { } proc haltsOnError(i: int): int { try! { canThrow(i); } catch e: DemoError { writeln("caught a DemoError"); } }
Defer statement¶
When an error is thrown, it is sometimes necessary to clean up state and
allocated memory. defer
statements facilitate that by running when a
scope is exited, regardless of how it is exited.
Example (defer.chpl).
proc deferredDelete(i: int) { try { var huge = allocateLargeObject(); defer { delete huge; writeln("huge has been deleted"); } canThrow(i); processObject(huge); } catch { writeln("no memory leaks"); } }
It is not possible to throw errors out of a defer
statement because
the atomicity of all defer
statements must be guaranteed, and the
handling context would be unclear.
Errors also cannot be thrown by deinit()
for similar reasons.
Errors and Methods¶
Errors can be thrown by methods, just as with any other function. An overriding method must throw if the overridden method throws, or not throw if the overridden method does not throw.
Example (throwing-methods.chpl).
class ThrowingObject { proc f() throws { throw new owned Error(); } } class SubThrowingObject : ThrowingObject { // must be marked throws even though it doesn't throw proc f() throws { writeln("this version doesn't throw"); } }
Errors and Multilocale¶
Errors can be thrown within on
statements. In that event, the error
will be propagated out of the on
statement.
Example (handle-from-on.chpl).
proc handleFromOn() { try { on Locales[0] { canThrow(1); } } catch { writeln("caught from Locale 0"); } }
Errors and Parallelism¶
TaskErrors¶
TaskErrors
class helps coordinate errors among groups of tasks by
collecting them for centralized handling. It can be iterated on and
filtered for different kinds of errors. See also
the module documentation for TaskErrors
.
Nested coforall
statements do not produce nested TaskErrors
.
Instead, the nested errors are flattened into the TaskErrors
error
thrown by the outer loop.
Errors and begin¶
Errors can be thrown within a begin
statement. In that event, the
error will be propagated to the sync
statement that waits for that
task.
Example (handle-from-begin.chpl).
proc handleFromBegin() { try! { sync { begin canThrow(0); begin canThrow(1); } } catch e: TaskErrors { writeln("caught from Locale 0"); } }
Errors and coforall and cobegin¶
Errors can be thrown from coforall
and cobegin
statements and
handled as TaskErrors
. The nested coforall
loops will emit a
flattened TaskErrors
error.
Example (handle-from-coforall.chpl).
proc handleFromCoforall() { try! { writeln("before coforall block"); coforall i in 1..2 { coforall j in 1..2 { throw new owned DemoError(); } } writeln("after coforall block"); } catch errors: TaskErrors { // not nested // all of e will be of runtime type DemoError in this example for e in errors { writeln("Caught task error e ", e!.message()); } } }
Example (handle-from-cobegin.chpl).
proc handleFromCobegin() { try! { writeln("before cobegin block"); cobegin { throw new owned DemoError(); throw new owned DemoError(); } writeln("after cobegin block"); } catch errors: TaskErrors { for e in errors { writeln("Caught task error e ", e!.message()); } } }
Errors and forall¶
Errors can be thrown from forall
loops, too. Although the forall
may execute serially within a single task, it will always throw a
TaskErrors
error if error(s) are thrown in the loop body.
Example (handle-from-forall.chpl).
proc handleFromForall() { try! { writeln("before forall block"); forall i in 1..2 { throw new owned DemoError(); } writeln("after forall block"); } catch errors: TaskErrors { for e in errors { writeln("Caught task error e ", e!.message()); } } }
Creating New Error Types¶
Errors in Chapel are implemented as classes, with a base class Error
defined in the standard modules. Error
may be used directly, and new
subclass hierarchies may be created from it. See also
the module documentation for Errors
.
A hierarchy for system errors is included in the OS
module,
accessed with a use
statement. See also
the module documentation for OS
.
Example (defining-errors.chpl).
use OS; class DemoError : Error { } class DemoSysError : SystemError { }
Error Handling Modes¶
Certain error handling details depend on the error handling mode:
Code in
prototype
modules (Prototype Modules), including implicit modules (Files and Implicit Modules), is handled in the prototype mode.Otherwise, code is handled in the production mode.
Code that is legal in the production mode is always legal in the prototype mode.
Prototype Mode¶
In the prototype mode, it is not necessary to explicitly handle errors
from a function that throws. If an error is thrown and the calling
function throws, the error will be propagated out of the function.
However, if an error is thrown and the calling function does not include
a throws
declaration, the program will halt.
In the following example, the code is in an implicit module. It is legal in the prototype mode:
Example (fatal-mode.chpl).
canThrow(1); // handling can be omitted; halts if an error occurs proc throwsErrorsOn() throws { // error propagates out of this function canThrow(-1); } proc doesNotThrowErrorsOn() { // causes a halt if called alwaysThrows(); }
The following module is explicitly marked as a prototype module, so the prototype mode applies here, too.
Example (PrototypeModule.chpl).
prototype module PrototypeModule { canThrow(1); // handling can be omitted; halts if an error occurs proc throwsErrorsOn() throws { // error propagates out of this function alwaysThrows(); } proc doesNotThrowErrorsOn() { // causes a halt if called alwaysThrows(); } proc canThrow(i: int): int throws { if i < 0 then throw new owned Error(); return i + 1; } }
Production Mode¶
In the production mode, it is necessary to handle errors if the calling function does not throw. If the calling function does throw, then the error will be propagated out, as with the prototype mode.
Example (ProductionModule.chpl).
module ProductionModule { // This would cause a compilation error since the error is not handled: // canThrow(1); proc throwsErrorsOn() throws { // any error thrown by alwaysThrows will propagate out alwaysThrows(); } // this function does not compile because the error is not handled // proc doesNotThrowErrorsOn() { // alwaysThrows(); // } }