Reading — step 1 of 7
Learn
Rust has no exceptions for ordinary errors. Instead, fallible operations return either Result<T, E> or Option<T> — and the compiler forces you to handle both cases. Chapter 9 of the Rust Book makes the case clearly: errors are values, not control flow.
Option<T>
For "the value might be missing":
enum Option<T> {
Some(T),
None,
}
fn find_user(id: u32) -> Option<String> {
if id == 1 {
Some(String::from("Ada"))
} else {
None
}
}
let name = find_user(1);
match name {
Some(n) => println!("found {}", n),
None => println!("not found"),
}
Result<T, E>
For "this might fail with a specific error":
enum Result<T, E> {
Ok(T),
Err(E),
}
fn parse_age(s: &str) -> Result<u32, String> {
s.trim().parse::<u32>()
.map_err(|_| format!("not a valid age: {}", s))
}
match parse_age("42") {
Ok(n) => println!("age: {}", n),
Err(e) => println!("error: {}", e),
}
The ? operator — early-return on Err/None
Manually matching every Result is verbose. The ? operator handles it for you:
fn add_two_inputs() -> Result<i32, std::num::ParseIntError> {
let mut a = String::new();
std::io::stdin().read_line(&mut a).unwrap();
let a: i32 = a.trim().parse()?; // returns early on Err
let mut b = String::new();
std::io::stdin().read_line(&mut b).unwrap();
let b: i32 = b.trim().parse()?;
Ok(a + b)
}
If any ? hits an Err, the function immediately returns that Err. On Ok, it unwraps the value.
? works for both Result and Option — but you can't mix them in one function unless you convert.
Useful helper methods
let x: Option<i32> = Some(5);
x.unwrap(); // panics on None — use sparingly
x.unwrap_or(0); // default if None
x.unwrap_or_else(|| compute_default());
x.expect("must be Some"); // panics with custom message
x.is_some(); // true
x.is_none(); // false
x.map(|n| n * 2); // Some(10)
x.and_then(|n| safe_div(100, n)); // chain Option-returning ops
Result has all the same plus error-mapping methods like .map_err().
panic! — when there's no recovering
fn impossible() -> ! {
panic!("this should never happen");
}
Use panic! for programmer errors (invariants violated, code paths that should be unreachable). For expected failures (parsing, I/O), return Result.
Why this design wins
In languages with exceptions, you can call any function and get a surprise crash from anywhere. In Rust:
- Every fallible signature says so:
Result<T, E>is in the type. - The compiler refuses to compile if you ignore an Err.
- No silent surprises. No "forgot to catch."
The trade-off: more verbose plumbing in error paths. The ? operator was added specifically to make that plumbing cheap.
Common mistakes
.unwrap()everywhere — panics propagate; production code should handle errors. Reserve for programmer-checked invariants.?outside aResult/Option-returning function — compile error.- Matching every Result manually when
?would do — verbose.?is idiomatic. - Returning
Result<T, &str>for libraries — fine for examples, but real libraries define error enums or useBox<dyn std::error::Error>.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…