Exception tracking
Nim supports exception tracking. The raises pragma can be used to explicitly define which exceptions a proc/iterator/method/converter is allowed to raise. The compiler verifies this:
proc p(what: bool) {.raises: [IOError, OSError].} =
if what: raise newException(IOError, "IO")
else: raise newException(OSError, "OS")
An empty raises list (raises: []) means that no exception may be raised:
proc p(): bool {.raises: [].} =
try:
unsafeCall()
result = true
except CatchableError:
result = false
A raises list can also be attached to a proc type. This affects type compatibility:
type
Callback = proc (s: string) {.raises: [IOError].}
var
c: Callback
proc p(x: string) =
raise newException(OSError, "OS")
c = p # type error
For a routine p, the compiler uses inference rules to determine the set of possibly raised exceptions; the algorithm operates on p’s call graph:
- Every indirect call via some proc type T is assumed to raise system.Exception (the base type of the exception hierarchy) and thus any exception unless T has an explicit raises list. However, if the call is of the form f(…) where f is a parameter of the currently analyzed routine it is ignored that is marked as .effectsOf: f. The call is optimistically assumed to have no effect. Rule 2 compensates for this case.
- Every expression e of some proc type within a call that is passed to parameter marked as .effectsOf of proc p is assumed to be called indirectly and thus its raises list is added to p’s raises list.
- Every call to a proc q which has an unknown body (due to a forward declaration) is assumed to raise system.Exception unless q has an explicit raises list. Procs that are importc’ed are assumed to have .raises: [], unless explicitly declared otherwise.
- Every call to a method m is assumed to raise system.Exception unless m has an explicit raises list.
- For every other call, the analysis can determine an exact raises list.
- For determining a raises list, the raise and try statements of p are taken into consideration.
Exceptions inheriting from system.Defect are not tracked with the .raises: [] exception tracking mechanism. This is more consistent with the built-in operations. The following code is valid:
proc mydiv(a, b): int {.raises: [].} =
a div b # can raise an DivByZeroDefect
And so is:
proc mydiv(a, b): int {.raises: [].} =
if b == 0: raise newException(DivByZeroDefect, "division by zero")
else: result = a div b
The reason for this is that DivByZeroDefect inherits from Defect and with --panics:on Defects become unrecoverable errors. (Since version 1.4 of the language.)