Language Tour

Error handling

Contracts

Functions can declare preconditions and postconditions, checked at runtime on every call. A failed contract throws ContractViolation.

def divide(a: Int, b: Int) -> Int {
    in {
        b != 0
    }
    return a // b
}

def abs_val(x: Int) -> Int {
    out (r) {
        r >= 0
    }
    if x < 0 {
        return -x
    }
    return x
}

in {} checks conditions before the body runs. out(r) {} checks the return value. old(expr) captures a value at function entry for comparison in postconditions. Classes and data types can declare invariant {} blocks. See Contracts for the full reference.

Try / except / finally

def divide(a: Int, b: Int) -> Int {
    in {
        b != 0
    }
    return a // b
}

try {
    let result = divide(10, 0)
} except ValueError as e {
    print("Bad value")
} except Error as e {
    print("Some other error")
} finally {
    print("Always runs")
}

raise ValueError("msg") throws an error. raise "msg" wraps in RuntimeError. Built-in error types: Error, RuntimeError, TypeError, ValueError, IOError, KeyError, IndexError, ContractViolation.

Result type

Result[T, E] represents success or failure without exceptions:

def safe_divide(a: Int, b: Int) -> Result[Int, Str] {
    if b == 0 {
        return Err("division by zero")
    }
    return Ok(a // b)
}

let r = safe_divide(10, 3)
match r {
    case Ok(value) {
        print(f"got {value}")
    }
    case Err(msg) {
        print(f"failed: {msg}")
    }
}

The ? postfix operator unwraps Ok or returns Err early from the enclosing function. Use ?else return for early return with a default value when unwrapping fails:

def get_name(id: Int) -> Str {
    let user = find_user(id) ?else return "unknown"
    return user.name
}

See Error handling for the full reference.

Defer

defer schedules a block to run when the enclosing scope exits, regardless of how it exits (normal return, exception, or early return). Deferred blocks run in reverse order of declaration:

def process_file(path: Str) -> None {
    let f = open(path)
    defer { close(f) }
    # work with f... cleanup happens automatically
}
← Types and data
Modules and more →