Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

5. Errors

When something goes wrong at run time — an index past the end of a list, a int("nope"), a key that isn't in a map — Korrin raises an exception. Left alone, it stops the program and prints a diagnostic. You can catch it instead.

try / except

try:
    n = int("not a number")
    print(n)
except err:
    print("could not parse: {err.message}")
# => could not parse: `int` cannot parse "not a number" as a number

The body runs; if it raises, the except block runs with the error bound to the name you give (err here). A caught built-in fault is an Error with two fields:

try:
    print([1, 2, 3][10])
except e:
    print(e.code)
    print(e.message)
# => E0307
# => index 10 is out of range for a list of length 3

If you don't need the value, leave the name out:

try:
    risky = 1 / 0
except:
    print("handled")
# => handled

raise

Raise your own error with raise. Error(message) is the usual thing to raise, but any value works:

fn withdraw(balance, amount):
    if amount > balance:
        raise Error("insufficient funds")
    return balance - amount

try:
    withdraw(100, 250)
except e:
    print(e.message)
# => insufficient funds

There is one except per try — it catches everything. To handle only some cases, check inside and re-raise the rest:

try:
    try:
        raise Error("timeout")
    except e:
        if e.message == "timeout":
            print("retrying")
        else:
            raise e
except outer:
    print("gave up: {outer.message}")
# => retrying

finally

A finally block runs no matter how the try exits — normal completion, a caught error, an uncaught one passing through, even a return:

fn read_config():
    try:
        return "config contents"
    finally:
        print("closing the file")

print(read_config())
# => closing the file
# => config contents

Because finally always runs, a return (or raise, or break) inside it wins over whatever the try was going to do — including swallowing an error. Use it for cleanup, not for control flow.

Uncaught errors

An exception with no try around it ends the program:

$ korrin run bank.kor
[E0312] Error: uncaught error: insufficient funds
   ╭─[bank.kor:8:9]
   │
 8 │         raise Error("insufficient funds")
   │               ─────────────┬────────────
   │                            ╰── raised here
───╯

Every built-in fault has a code (E0307, E0304, …); the error reference lists them, and they are what err.code gives you.