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.
Rust encodes "might be missing" and "might fail" in types, so the compiler makes you handle them before you can use the value.
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:
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 canmatchon variants, exhaustively. - Applications often collect errors into
anyhow::Errorand 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.