Rust by Example: Option Handling
Rust 1.75+
Say goodbye to null pointer exceptions. This example demonstrates how to use the Option type to represent the presence or absence of a value, and how to safely handle these cases using combinators like map, unwrap_or, and if let.
Code
fn main() {
let some_number = Some(5);
let no_number: Option<i32> = None;
// 1. Using unwrap (Dangerous!)
// Panics if the value is None
let x = some_number.unwrap();
println!("Unwrapped: {}", x);
// 2. Using expect (Better panic message)
// let y = no_number.expect("We expected a number here!");
// 3. Using unwrap_or (Safe default)
let z = no_number.unwrap_or(0);
println!("Defaulted: {}", z);
// 4. Using map (Transform inner value)
let doubled = some_number.map(|n| n * 2);
println!("Doubled: {:?}", doubled); // Some(10)
// 5. Using and_then (Chain operations)
let result = some_number
.and_then(|n| checked_division(n, 2.5)) // Returns Option
.and_then(|n| checked_division(n, 0.0)); // Returns None
println!("Chained result: {:?}", result);
// 6. if let (Concise matching)
if let Some(n) = some_number {
println!("Found number: {}", n);
}
}
fn checked_division(num: i32, div: f64) -> Option<f64> {
if div == 0.0 {
None
} else {
Some(num as f64 / div)
}
}Explanation
The Option<T> type is Rust's solution to the "billion-dollar mistake" of null references. It forces you to handle the possibility of absence explicitly. Under the hood, Rust uses Niche Optimization to make Option efficient. For example, Option<&T> takes up the same amount of memory as a raw pointer, with None represented by the null address (0), which is invalid for a reference.
To work with Options ergonomically, Rust provides "combinators":
map(): Transforms the value insideSome. If the Option isNone, it returnsNoneimmediately. This is lazy; the closure only runs if a value exists.and_then(): Also known as flatmap. It is used when the transformation itself returns an Option. It prevents nested types likeOption<Option<T>>.unwrap_or_else(): Similar tounwrap_or, but takes a closure. This allows for lazy evaluation of the default value, which is crucial if computing the default is expensive (e.g., a database call).
Code Breakdown
13
unwrap_or(0). This is the safe way to extract a value. If no_number is Some(v), it returns v. If it is None, it returns 0. No panic occurs.17
map(|n| n * 2). This applies the closure n * 2 to the value inside the Option if it exists. If some_number was None, the closure would never run, and the result would be None.28
if let Some(n) = .... This is syntactic sugar for a match statement that only cares about one variant. It reads: "If some_number matches the pattern Some(n), execute this block."
