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

Choose which error details to keep

Keep a concrete error type when the caller needs the cause. For example, IoError preserves an input/output cause, while str can carry a message.

Pass an error back unchanged when the caller can use it directly. When several operations have different errors, the next lesson shows how to keep them together without losing their details.

Sometimes an API needs to add meaning to a lower-level cause. Here, ConfigError.InvalidCount identifies what was being parsed and retains the original ParseError. A Result’s map_error method changes just the error type, forwarding success unchanged:

enum ConfigError:
    InvalidCount(ParseError)

def read_count(text: &str) -> Result[i64, ConfigError]:
    i64.parse(text).map_error(ConfigError.InvalidCount)

def main() -> Result[(), IoError]:
    match read_count("many"):
        case Ok(count):
            print(count)?
        case Err(ConfigError.InvalidCount(cause)):
            print(cause == ParseError.Invalid)?
    Ok(())
True

ConfigError.InvalidCount written without arguments is a function from ParseError to ConfigError. map_error calls it once with the original error, only when parsing fails. On success, the parsed count passes through unchanged. The conversion policy is explicit, and mapping adds no allocation. read_count returns that result directly because it has no more work to do. Use ? when further work depends on the successful value. See the mapping reference for callback evaluation and ownership details.

When the new error needs more than the cause, such as a second payload or captured context, pass an anonymous function that builds it instead.

The example’s main deliberately handles a failed parse so you can see that the cause survived. In an application that cannot continue, propagate the error instead. Returning an error from main reports it on standard error and exits with status one; printing an error and returning Ok(()) reports success to the program’s caller. The command-line application puts this policy into practice.

Avoid replacing useful causes with builtin Failure: propagation into Result[T, Failure] discards the original error and keeps only Failure.Unspecified. It is a last resort, not a way to combine errors. See the reference for its exact erasure rules.

.unwrap() explicitly extracts a successful payload or terminates on Err or Nothing, without normal scope cleanup:

def main() -> Result[(), IoError]:
    found = Some(42)
    print(found.unwrap())?
    Ok(())
42

Choose .unwrap() only when termination is your intended policy.