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.