Concept · Rust

Result, Option and ?

Rust has no null and no exceptions: absence is Option<T>, failure is Result<T, E>, and ? propagates both in one character.

3 minquestion mark operatorthiserroranyhowunwrap

Rust encodes "might be missing" and "might fail" in types, so the compiler makes you handle them before you can use the value.

rust
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }

An Option<User> isn't a User; you can't call its methods until you've dealt with None. That turns forgotten null checks into compile errors. A Result is #[must_use], so ignoring one produces a warning.

The ? operator

? unwraps Ok/Some, or returns early from the current function with the Err/None:

rust
fn read_port(path: &str) -> Result<u16, Box<dyn std::error::Error>> {
    let text = std::fs::read_to_string(path)?;   // io::Error
    let port = text.trim().parse::<u16>()?;      // ParseIntError
    Ok(port)
}

On the way out it converts the error with From::from, which is how one function can propagate several error types into its own. It plays the role of Go's if err != nil { return err } — see errors as values and wrapping — in one character.

Choosing error types

  • Libraries return a typed error enum (often derived with thiserror) so callers can match on variants, exhaustively.
  • Applications often collect errors into anyhow::Error and add context with .context("loading config") as they propagate.
  • Panics (unwrap, expect, panic!) are for bugs and broken invariants, never for input a user or the network can control.

Handy combinators

map, and_then, unwrap_or, unwrap_or_else, ok_or (Option → Result) and ok() (Result → Option) cover most transformations without a match. Collecting an iterator of Results into Result<Vec<_>, _> stops at the first error.

The error handling lesson builds custom error types and compares thiserror with anyhow.

Learn it in depth