Error handling

attempt / rescue

attempt wraps code that might fail. rescue catches the error:

lhj
attempt:
    forge x = int("not a number")
rescue e:
    echo "Error: {e}"

Output:

lhj
Error: invalid literal for int() with base 10: 'not a number'

rescue with type filtering

You can match specific error types:

lhj
attempt:
    forge result = 10 / 0
rescue ZeroDivisionError as e:
    echo "Division by zero: {e}"
rescue RuntimeError as e:
    echo "Runtime problem: {e}"
rescue e:
    echo "Something else: {e}"

finally — always runs

lhj
attempt:
    forge f = os.open("data.txt")
    forge content = f.read()
rescue e:
    echo "Read failed: {e}"
finally:
    echo "Cleanup done"

The finally block runs whether the attempt succeeded or not.

raise — throwing errors

lhj
blade divide(a, b):
    blade b == 0:
        raise "Cannot divide by zero"
    release a / b

attempt:
    echo divide(10, 0)
rescue e:
    echo e    ## Cannot divide by zero

Custom error types

Create a class that extends a base error:

lhj
ritual ValidationError extends RuntimeError:
    blade awaken(self, field: str, msg: str):
        self.field = field
        self.msg   = msg
        invoke awaken(self, "{field}: {msg}")

blade validate_hp(hp: int):
    blade hp < 0:
        raise ValidationError("hp", "must be non-negative, got {hp}")
    blade hp > 1000:
        raise ValidationError("hp", "maximum is 1000, got {hp}")

attempt:
    validate_hp(-5)
rescue ValidationError as e:
    echo "Validation failed on field '{e.field}': {e.msg}"

Error types

TypeWhen raised
RuntimeErrorGeneral interpreter errors
NameErrorUndefined variable
TypeErrorWrong type for an operation
ValueErrorRight type, wrong value
ZeroDivisionErrorDivision or modulo by zero
IndexErrorList index out of range
KeyErrorDict key not found
AttributeErrorObject has no such attribute

Propagation

If an error isn't caught in the current function, it propagates up the call stack. If it reaches the top level without being caught, the interpreter prints the error and exits.

lhj
blade inner():
    raise "boom"

blade middle():
    inner()

blade outer():
    attempt:
        middle()
    rescue e:
        echo "Caught in outer: {e}"

outer()    ## Caught in outer: boom