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

Represent a recoverable error

Use Result[T, E] when failure has an explanation. Ok(value) carries success data of type T; Err(error) carries a cause of type E.

def divide(left: i64, right: i64) -> Result[i64, str]:
    outcome = checked:
        left // right
    match outcome:
        case Ok(value):
            Ok(value)
        case Err(error):
            match error:
                case ZeroDivisionError():
                    Err("division by zero")
                case OverflowError(_):
                    Err("division overflow")

def show(left: i64, right: i64) -> Result[(), IoError]:
    match divide(left, right):
        case Ok(value):
            print(value)?
        case Err(message):
            print(message)?
    Ok(())

def main() -> Result[(), IoError]:
    show(8, 2)?
    show(8, 0)?
    Ok(())
4
division by zero

The local checked: block collects division failures. Matching the error distinguishes division by zero from signed overflow (i64.MIN // -1), then gives each a message. This small example deliberately uses strings as its error API; applications can preserve those distinct typed errors instead.

The caller can recover and continue, as show does here. A result does not implicitly unwrap itself or raise an exception.

When success has no data, use unit as the success type:

def validate(name: str) -> Result[(), str]:
    if len(name) == 0:
        return Err("name is empty")
    Ok(())

def show(name: str) -> Result[(), IoError]:
    match validate(name):
        case Ok(_):
            print("valid")?
        case Err(message):
            print(message)?
    Ok(())

def main() -> Result[(), IoError]:
    show("Plenty")?
    show("")?
    Ok(())
valid
name is empty

The pattern _ ignores a payload you do not need. Ok(()) represents success without data; Nothing represents absence.

is_ok() and is_err() answer whether a result succeeded without looking at its payload. They borrow the result, so it can still be matched or returned.

def divide(left: i64, right: i64) -> Result[i64, str]:
    outcome = checked:
        left // right
    match outcome:
        case Ok(value):
            Ok(value)
        case Err(error):
            match error:
                case ZeroDivisionError():
                    Err("division by zero")
                case OverflowError(_):
                    Err("division overflow")

def main() -> Result[(), IoError]:
    outcome = divide(8, 0)
    print(outcome.is_ok(), outcome.is_err())?
    print(divide(8, 2).is_ok())?
    match outcome:
        case Ok(value):
            print(value)?
        case Err(message):
            print(message)?
    Ok(())
False True
True
division by zero