Exception Handling
Exception handling
Try statement
Example:
# read the first two lines of a text file that should contain numbers
# and tries to add them
var
f: File
if open(f, "numbers.txt"):
try:
var a = readLine(f)
var b = readLine(f)
echo "sum: " & $(parseInt(a) + parseInt(b))
except OverflowDefect:
echo "overflow!"
except ValueError, IOError:
echo "catch multiple exceptions!"
except CatchableError:
echo "Catchable exception!"
finally:
close(f)
The statements after the try
are executed in sequential order unless
an exception e
is raised. If the exception type of e
matches any
listed in an except
clause, the corresponding statements are executed.
The statements following the except
clauses are called
exception handlers
.
If there is a finally
clause, it is
always executed after the exception handlers.
The exception is consumed in an exception handler. However, an
exception handler may raise another exception. If the exception is not
handled, it is propagated through the call stack. This means that often
the rest of the procedure - that is not within a finally
clause -is
not executed (if an exception occurs).
Try expression
Try can also be used as an expression; the type of the try
branch then
needs to fit the types of except
branches, but the type of the
finally
branch always has to be `void`:
from std/strutils import parseInt
let x = try: parseInt("133a")
except ValueError: -1
finally: echo "hi"
To prevent confusing code there is a parsing limitation; if the try
follows a (
it has to be written as a one liner:
Except clauses
Within an except
clause it is possible to access the current exception
using the following syntax:
Alternatively, it is possible to use getCurrentException
to retrieve
the exception that has been raised:
Note that getCurrentException
always returns a ref Exception
type.
If a variable of the proper type is needed (in the example above,
IOError
), one must convert it explicitly:
try:
# ...
except IOError:
let e = (ref IOError)(getCurrentException())
# "e" is now of the proper type
However, this is seldom needed. The most common case is to extract an
error message from e
, and for such situations, it is enough to use
`getCurrentExceptionMsg`:
Custom exceptions
It is possible to create custom exceptions. A custom exception is a custom type:
Ending the custom exception\'s name with Error
is recommended.
Custom exceptions can be raised just like any other exception, e.g.:
Defer statement
Instead of a try finally
statement a defer
statement can be used,
which avoids lexical nesting and offers more flexibility in terms of
scoping as shown below.
Any statements following the defer
in the current block will be
considered to be in an implicit try block:
Is rewritten to:
When defer
is at the outermost scope of a template/macro, its scope
extends to the block where the template is called from:
template safeOpenDefer(f, path) =
var f = open(path, fmWrite)
defer: close(f)
template safeOpenFinally(f, path, body) =
var f = open(path, fmWrite)
try: body # without `defer`, `body` must be specified as parameter
finally: close(f)
block:
safeOpenDefer(f, "/tmp/z01.txt")
f.write "abc"
block:
safeOpenFinally(f, "/tmp/z01.txt"):
f.write "abc" # adds a lexical scope
block:
var f = open("/tmp/z01.txt", fmWrite)
try:
f.write "abc" # adds a lexical scope
finally: close(f)
Top-level defer
statements are not supported since it\'s unclear what
such a statement should refer to.
Raise statement
Example:
Apart from built-in operations like array indexing, memory allocation,
etc. the raise
statement is the only way to raise an exception.
If no exception name is given, the current exception is
re-raised
. The
ReraiseDefect
exception is raised if
there is no exception to re-raise. It follows that the raise
statement
always raises an exception.
Exception hierarchy
The exception tree is defined in the system module. Every
exception inherits from system.Exception
. Exceptions that indicate
programming bugs inherit from system.Defect
(which is a subtype of
Exception
) and are strictly speaking not catchable as they can also be
mapped to an operation that terminates the whole process. If panics are
turned into exceptions, these exceptions inherit from Defect
.
Exceptions that indicate any other runtime error that can be caught
inherit from system.CatchableError
(which is a subtype of
Exception
).
Imported exceptions
It is possible to raise/catch imported C++ exceptions. Types imported
using importcpp
can be raised or caught. Exceptions are raised by
value and caught by reference. Example:
type
CStdException {.importcpp: "std::exception", header: "<exception>", inheritable.} = object
## does not inherit from `RootObj`, so we use `inheritable` instead
CRuntimeError {.requiresInit, importcpp: "std::runtime_error", header: "<stdexcept>".} = object of CStdException
## `CRuntimeError` has no default constructor => `requiresInit`
proc what(s: CStdException): cstring {.importcpp: "((char *)#.what())".}
proc initRuntimeError(a: cstring): CRuntimeError {.importcpp: "std::runtime_error(@)", constructor.}
proc initStdException(): CStdException {.importcpp: "std::exception()", constructor.}
proc fn() =
let a = initRuntimeError("foo")
doAssert $a.what == "foo"
var b: cstring
try: raise initRuntimeError("foo2")
except CStdException as e:
doAssert e is CStdException
b = e.what()
doAssert $b == "foo2"
try: raise initStdException()
except CStdException: discard
try: raise initRuntimeError("foo3")
except CRuntimeError as e:
b = e.what()
except CStdException:
doAssert false
doAssert $b == "foo3"
fn()
Note: getCurrentException()
and getCurrentExceptionMsg()
are not
available for imported exceptions from C++. One needs to use the
except ImportedException as x:
syntax and rely on functionality of the
x
object to get exception details.