Option & Result as Enums
Option<T> models presence or absence. Result<T, E> models success or recoverable failure. Together they replace null and exceptions.
Recipe
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
fn read_port(s: &str) -> Result<u16, std::num::ParseIntError> {
s.parse()
}
fn main() {
println!("{:?} {:?}", divide(10.0, 2.0), read_port("8080"));
}When to reach for this: Any optional value (Option) and any fallible operation (Result).
Working Example
fn first_line(path: &str) -> Result<Option<String>, std::io::Error> {
let mut lines = std::fs::read_to_string(path)?.lines();
Ok(lines.next().map(|s| s.to_string()))
}
fn main() {
match first_line("Cargo.toml") {
Ok(Some(l)) => println!("{l}"),
Ok(None) => println!("empty"),
Err(e) => eprintln!("{e}"),
}
}What this demonstrates:
Resultfor I/O failureOptioninsideOkfor empty file?propagatesio::Error- Nested enums compose cleanly
Deep Dive
Definitions
enum Option<T> { None, Some(T) }
enum Result<T, E> { Ok(T), Err(E) }Conversions
Option->Result:ok_or,ok_or_elseResult->Option:ok,err?onResultin functions returningResult
Option is not error handling
Use Result when caller needs failure reason. Option for legitimately missing data.
Gotchas
unwrapin library code - Panics caller. Fix: ReturnResultorOption.Nonefor errors - Loses context. Fix:Resultwith error type.- Mixing
?on Option in Result fn - NeedsFromorok_or. Fix:ok_or(MyError::Missing)?or?inOptionreturning fn only. - Huge error types in
Result- Allocation cost. Fix:Box<dyn Error>orthiserrorenums in apps. - Chaining too deep without
?- Nestedmatchhell. Fix:?and combinators.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
panic! | Programmer bug, impossible branch | Expected failures |
| Custom enum | Closed error set in domain | Result suffices |
anyhow in binaries | Flexible errors | Public library API |
| Sentinel values | Never in safe Rust APIs | - |
FAQs
Why not null?
Null conflates missing and error. `Option`/`Result` separate cases at type level.Collect `Vec<Result>`?
`collect::<Result<Vec<_>, _>>()` short-circuits on first `Err`.`unwrap_or_default`?
Needs `T: Default` for `None` path.Compare Results?
Only if `T: PartialEq` and `E: PartialEq` - often avoid.`try` blocks?
Unstable/experimental in some forms - use `?` in fn returning `Result`.Option in struct fields?
Common for partial records from APIs/DB.Result in iterator?
Use `Iterator<Item = Result<T,E>>` or `flatten` patterns.Void success?
`Result<(), E>` for ops with no value on success.Nested Option?
`Option<Option<T>>` rare - flatten with `and_then`.serde with Option?
Skips `None` fields by default with `skip_serializing_if`.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.