Error Handling

16.1 Exceptions

Hierarchy: Exception → CatchableError (recoverable: IOError, ValueError, KeyError, OSError...) and Defect (programming bugs: IndexDefect, RangeDefect, NilAccessDefect, AssertionDefect, OverflowDefect, FieldDefect, DivByZeroDefect).

Defects should not be caught. With --panics:on, they abort the program immediately.

type
  AppError = object of CatchableError
  ValidationError = object of AppError
    field: string

proc validateAge(age: int) =
  if age < 0:
    raise newException(ValidationError, "age must be non-negative")

try:
  validateAge(-1)
except ValidationError as e:
  echo "Validation: ", e.msg
except IOError, OSError:
  echo "IO problem: ", getCurrentExceptionMsg()
except CatchableError:
  echo "something else"
else:
  echo "no error"
finally:
  echo "cleanup"

16.2 Raising and Re-raising

try: riskyOp()
except ValueError as e:
  log(e.msg)
  raise                                          # re-raise the current exception
raise (ref ValueError)(msg: "custom", parent: nil)

16.3 defer, try as an Expression

let n = try: parseInt(input) except ValueError: 0

16.4 Assertions

assert x > 0, "x must be positive"      # disabled with -d:danger / --assertions:off
doAssert x > 0                          # always checked

16.5 Result-Based Error Handling

The results package (from Status) provides Result[T, E] with ? propagation, valueOr, isOk, mapErr:

std/options for optional values. Exceptions vs results is a design choice. Effect tracking (Module 20) makes exceptions explicit.

import results
proc parsePort(s: string): Result[int, string] =
  try:
    let p = parseInt(s)
    if p in 1..65535: ok(p) else: err("out of range")
  except ValueError: err("not a number")

proc connect(s: string): Result[void, string] =
  let port = ? parsePort(s)       # early return on error
  echo "connecting to ", port
  ok()

16.6 Stack Traces

Enabled in debug mode. --stackTrace:on, --lineTrace:on, getStackTrace(), e.getStackTraceEntries().