The Option Type
No Null in Rust
Section titled “No Null in Rust”Many languages allow any variable to hold a “null” or “nil” value, leading to null-pointer exceptions at runtime. Rust takes a different approach: there is no null. When a value might be absent, you use Option<T> from the standard library.
Option<T> is an enum defined as:
enum Option<T> { Some(T), None,}Some(value) wraps a present value of type T. None represents the absence of a value. The compiler forces you to handle both cases before you can use the inner value — null-pointer bugs become compile-time errors.
Because Option<T> is so common, Some and None are in scope everywhere without needing Option:: prefix.
Handling Option<T> with match
Section titled “Handling Option<T> with match”The most explicit way to handle an Option<T> is a match expression:
fn divide(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) }}
match divide(10.0, 2.0) { Some(result) => println!("Result: {}", result), None => println!("Cannot divide by zero"),}The compiler rejects code that only handles Some without None — the match must be exhaustive.
if let for the Happy Path
Section titled “if let for the Happy Path”When you only care about the Some case, if let is cleaner:
if let Some(value) = divide(10.0, 2.0) { println!("Got: {}", value);}The body executes only when the value is Some. Add else to handle None if needed.
unwrap_or — Providing a Default
Section titled “unwrap_or — Providing a Default”unwrap_or(default) returns the inner value if Some, or the given default if None:
let maybe: Option<i32> = None;let value = maybe.unwrap_or(0); // 0This is the safest form of “just give me a value or a fallback.” Avoid plain .unwrap() in production code — it panics on None.
map — Transforming the Inner Value
Section titled “map — Transforming the Inner Value”map applies a closure to the Some value and returns a new Option<T>. If the original is None, it passes None through unchanged:
let doubled = Some(5).map(|x| x * 2); // Some(10)let nothing: Option<i32> = None;let still_none = nothing.map(|x| x * 2); // Nonemap is essential for chaining transformations without unwrapping intermediate Option<T> values.
fn divide(a: f64, b: f64) -> Option<f64> { if b == 0.0 { None } else { Some(a / b) }}
fn find_first_even(nums: &[i32]) -> Option<i32> { nums.iter().find(|&&x| x % 2 == 0).copied()}
fn main() { // match on Option match divide(10.0, 3.0) { Some(result) => println!("10 / 3 = {:.4}", result), None => println!("Cannot divide by zero"), }
match divide(5.0, 0.0) { Some(result) => println!("result = {}", result), None => println!("Cannot divide by zero"), }
// if let let numbers = vec![1, 3, 5, 8, 9]; if let Some(even) = find_first_even(&numbers) { println!("First even: {}", even); }
// unwrap_or let missing: Option<i32> = None; println!("Default: {}", missing.unwrap_or(0));
// map let some_val: Option<i32> = Some(5); let doubled = some_val.map(|x| x * 2); println!("Doubled: {:?}", doubled);}Compiling…