Rust error handling: Option and Result
Categories: Study Notes
Tags: Rust Error Handling Option Result
(Eng) Rust error handling: Option and Result
Rust turns many runtime errors into compile-time guarantees through two enums: Option<T> and Result<T, E>. Unlike null-pointer exceptions, you cannot ignore these values. The compiler forces you to handle them before using the underlying data.
Missing-value and failure cases become explicit types, not hidden assumptions.
Option: a value that may not exist enum Option<T> {
Some(T),
None,
}
Use Option when a value is optional.
let text = map.get("missing");
match text {
Some(t) => println!("{}", t),
None => println!("not found"),
}
Shorthand forms:
if let Some(t) = text {
println!("{}", t);
}
let t = text.unwrap_or("default");
Result<T, E>: an operation that may fail
enum Result<T, E> {
Ok(T),
Err(E),
}
Use Result for operations that can fail with a typed reason.
match fs::read_to_string(path) {
Ok(data) => process(data),
Err(Error::NotFound) => eprintln!("missing file"),
Err(Error::PermissionDenied) => eprintln!("access denied"),
Err(e) => eprintln!("io failed: {}", e),
}
Pattern matching as try/catch
The closest equivalent to try/catch with per-error behavior is match on Result.
Pattern Purpose match result { ... }exhaustive per-error handling if let Err(e) = result { ... }handle one variant `result.map_err( e ? operatorreturn error to caller immediately
match parse_config(path) {
Ok(cfg) => run(cfg),
Err(ConfigError::Missing(key)) => eprintln!("missing key: {}", key),
Err(ConfigError::Parse(e)) => eprintln!("parse failed: {}", e),
Err(e) => return Err(e),
}
The ? operator
? unwraps Ok and returns Err early from the current function. It replaces nested match for linear happy-path flow.
fn load() -> Result<Config, ConfigError> {
let text = fs::read_to_string(path)?;
let cfg = parse(&text)?;
Ok(cfg)
}
Why not exceptions
Exceptions hide control flow. A function can return normally or throw from anywhere, making reasoning about state harder. Rust errors are values. They flow through the type system, are explicit in signatures, and cannot be silently ignored.
Core lesson: handle missing data with Option, handle recoverable failure with Result, and reserve panics for unrecoverable program invariants.
enum Option<T> {
Some(T),
None,
}
Use Option when a value is optional.
let text = map.get("missing");
match text {
Some(t) => println!("{}", t),
None => println!("not found"),
}
Shorthand forms:
if let Some(t) = text {
println!("{}", t);
}
let t = text.unwrap_or("default");
Result<T, E>: an operation that may fail
enum Result<T, E> {
Ok(T),
Err(E),
}
Use Result for operations that can fail with a typed reason.
match fs::read_to_string(path) {
Ok(data) => process(data),
Err(Error::NotFound) => eprintln!("missing file"),
Err(Error::PermissionDenied) => eprintln!("access denied"),
Err(e) => eprintln!("io failed: {}", e),
}
Pattern matching as try/catch
The closest equivalent to try/catch with per-error behavior is match on Result.
| Pattern | Purpose |
|---|---|
match result { ... } | exhaustive per-error handling |
if let Err(e) = result { ... } | handle one variant |
| `result.map_err( | e |
? operator | return error to caller immediately |
match parse_config(path) {
Ok(cfg) => run(cfg),
Err(ConfigError::Missing(key)) => eprintln!("missing key: {}", key),
Err(ConfigError::Parse(e)) => eprintln!("parse failed: {}", e),
Err(e) => return Err(e),
}
The ? operator
? unwraps Ok and returns Err early from the current function. It replaces nested match for linear happy-path flow.
fn load() -> Result<Config, ConfigError> {
let text = fs::read_to_string(path)?;
let cfg = parse(&text)?;
Ok(cfg)
}
Why not exceptions
Exceptions hide control flow. A function can return normally or throw from anywhere, making reasoning about state harder. Rust errors are values. They flow through the type system, are explicit in signatures, and cannot be silently ignored.
Core lesson: handle missing data with
Option, handle recoverable failure withResult, and reserve panics for unrecoverable program invariants.