Errors in Nim

In Nim, it’s idiomatic to communicate errors via an explicit return value or by raising exceptions. This approach makes it easy to see which functions can fail and to handle errors using the same language constructs employed for other, non-error tasks.

import std/strformat

# By convention, errors are often returned as the last value
# and have type Exception or a custom error type.
proc f(arg: int): tuple[result: int, error: ref Exception] =
  if arg == 42:
    # We can create a new Exception with a custom message
    return (result: -1, error: newException(ValueError, "can't work with 42"))
  
  # A nil value in the error position indicates that there was no error
  return (result: arg + 3, error: nil)

# A custom error type can be defined as an object that inherits from Exception
type
  OutOfTeaError = object of Exception
  PowerError = object of Exception

# We can create singleton instances of our custom errors
let
  ErrOutOfTea = newException(OutOfTeaError, "no more tea available")
  ErrPower = newException(PowerError, "can't boil water")

proc makeTea(arg: int): ref Exception =
  if arg == 2:
    return ErrOutOfTea
  elif arg == 4:
    # We can wrap errors with higher-level errors to add context
    return newException(Exception, fmt"making tea: {ErrPower.msg}")
  return nil

proc main() =
  for i in [7, 42]:
    # It's common to use pattern matching for error checking
    let (r, e) = f(i)
    if e != nil:
      echo "f failed: ", e.msg
    else:
      echo "f worked: ", r

  for i in 0..4:
    let err = makeTea(i)
    if err != nil:
      # We can use the `of` operator to check for specific error types
      if err of OutOfTeaError:
        echo "We should buy new tea!"
      elif err of PowerError:
        echo "Now it is dark."
      else:
        echo "unknown error: ", err.msg
      continue
    echo "Tea is ready!"

main()

To run this program, save it as errors.nim and use the Nim compiler:

$ nim c -r errors.nim
f worked: 10
f failed: can't work with 42
Tea is ready!
Tea is ready!
We should buy new tea!
Tea is ready!
Now it is dark.

In this Nim version:

  1. We use tuples to return both a result and an error from the f function.
  2. Custom error types are defined as objects inheriting from Exception.
  3. We use newException to create error instances.
  4. Error checking is done using pattern matching and the of operator for type checking.
  5. The fmt string interpolation is used for formatting error messages.

Nim’s approach to error handling combines aspects of both exception-based and return value-based error handling, allowing for flexible and expressive error management.