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
catchblocks.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 preventstryfrom 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 matchingcatchclause 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.
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"); } }
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"); } }
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.
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"); } }
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()); } } }
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
prototypemodules (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(); // } }