โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
โข Option
โข Result
โข The ? operator propagates errors up the call stack concisely
โข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations
โข The compiler forces you to handle every error case โ no silent failures
match user.get("alice") {
Some(age) => match age.checked_mul(*age) {
Some(squared) => println!("{}", squared),
None => println!("overflow"),
},
None => println!("user not found"),
}
Option combinators flatten this into a single readable chain. The `None` case propagates automatically โ you only describe the happy path.
fn safe_sqrt(x: f64) -> Option<f64> {
if x >= 0.0 { Some(x.sqrt()) } else { None }
}
let some5: Option<i32> = Some(5);
let none: Option<i32> = None;
// .map(): transform Some, leave None alone
some5.map(|x| x * 2) // Some(10)
none.map(|x| x * 2) // None โ the closure never runs
// .filter(): keep Some only if the value passes the test
Some(5i32).filter(|&x| x % 2 == 0) // None โ 5 is odd
Some(6i32).filter(|&x| x % 2 == 0) // Some(6) โ 6 is even
// .and_then(): chain optional computations
// Each step can return None, short-circuiting the rest
let result = Some("4.0")
.and_then(|s| s.parse::<f64>().ok()) // parse string โ None if invalid
.and_then(safe_sqrt); // sqrt โ None if negative
// Some(2.0)
// None short-circuits the whole chain
let result2: Option<f64> = None
.and_then(|s: &str| s.parse::<f64>().ok())
.and_then(safe_sqrt);
// None โ nothing runs after the first None
// .or() and .or_else(): provide fallbacks
none.or(Some(42)) // Some(42)
none.or_else(|| Some(99)) // Some(99) โ or_else is lazy (closure runs only if needed)
// Real example: HashMap lookup โ transform
use std::collections::HashMap;
let users = HashMap::from([("alice", 30u32)]);
let age_squared = users.get("alice").map(|&age| age * age);
// Some(900)
let missing = users.get("bob").map(|&age| age * age);
// None
The `&` inside `.filter(|&x| ...)` pattern-matches the reference โ it's pulling the value out of the `&i32`. You'll see this often with Option and iterator closures.
| Concept | OCaml | Rust |
|---|---|---|
| Map Some value | `Option.map f opt` | `opt.map(f)` |
| Chain optional ops | `Option.bind opt f` | `opt.and_then(f)` |
| Filter by predicate | Manual `match` | `opt.filter(pred)` |
| Default value | `Option.value ~default opt` | `opt.unwrap_or(default)` |
| Lazy fallback | `Option.value_or_thunk` | `opt.unwrap_or_else(f)` |
//! 292. map(), filter(), and_then() on Option
//!
//! Option combinators replace verbose match expressions with composable chains.
fn safe_sqrt(x: f64) -> Option<f64> {
if x >= 0.0 { Some(x.sqrt()) } else { None }
}
fn main() {
let some5: Option<i32> = Some(5);
let none: Option<i32> = None;
// map: transform Some, pass through None
println!("map Some(5)*2: {:?}", some5.map(|x| x * 2));
println!("map None*2: {:?}", none.map(|x| x * 2));
// filter: keep Some only if predicate holds
println!("filter even Some(5): {:?}", some5.filter(|&x| x % 2 == 0));
println!("filter even Some(6): {:?}", Some(6i32).filter(|&x| x % 2 == 0));
// and_then: chain optional computations (monadic bind)
let config_str: Option<&str> = Some("4.0");
let result = config_str
.and_then(|s| s.parse::<f64>().ok())
.and_then(safe_sqrt);
println!("Parse and sqrt '4.0': {:?}", result);
// Short-circuit on None
let bad: Option<&str> = None;
let result2 = bad.and_then(|s| s.parse::<f64>().ok()).and_then(safe_sqrt);
println!("None chain: {:?}", result2);
// or and or_else: provide defaults
let default = none.or(Some(42));
println!("None.or(Some(42)): {:?}", default);
let computed = none.or_else(|| {
println!(" (computing default...)");
Some(99)
});
println!("or_else: {:?}", computed);
// Chain of operations on a user lookup
let users = std::collections::HashMap::from([
("alice", 30u32), ("bob", 25),
]);
let age_squared = users.get("alice")
.map(|&age| age * age);
println!("alice's ageยฒ: {:?}", age_squared);
}
#[cfg(test)]
mod tests {
#[test]
fn test_map_some() {
assert_eq!(Some(5i32).map(|x| x * 2), Some(10));
}
#[test]
fn test_map_none() {
assert_eq!(None::<i32>.map(|x| x * 2), None);
}
#[test]
fn test_filter() {
assert_eq!(Some(4i32).filter(|&x| x % 2 == 0), Some(4));
assert_eq!(Some(3i32).filter(|&x| x % 2 == 0), None);
}
#[test]
fn test_and_then_chain() {
let result = Some("4.0")
.and_then(|s| s.parse::<f64>().ok())
.map(|x| x * x);
assert!((result.unwrap() - 16.0).abs() < 1e-10);
}
#[test]
fn test_or_default() {
let result = None::<i32>.or(Some(42));
assert_eq!(result, Some(42));
}
}
(* 292. map(), filter(), and_then() on Option - OCaml *)
let () =
let some_5 = Some 5 in
let none : int option = None in
(* map: transforms Some value, passes through None *)
Printf.printf "map Some(5)*2: %s\n"
(match Option.map (fun x -> x * 2) some_5 with Some n -> string_of_int n | None -> "None");
Printf.printf "map None*2: %s\n"
(match Option.map (fun x -> x * 2) none with Some n -> string_of_int n | None -> "None");
(* bind/and_then: chain optional computations *)
let safe_div x y = if y = 0 then None else Some (x / y) in
let result = Option.bind some_5 (fun n -> safe_div 10 n) in
Printf.printf "10/5 chained: %s\n"
(match result with Some n -> string_of_int n | None -> "None");
(* filter *)
let even = Option.filter (fun x -> x mod 2 = 0) some_5 in
Printf.printf "filter even Some(5): %s\n"
(match even with Some n -> string_of_int n | None -> "None");
let even6 = Option.filter (fun x -> x mod 2 = 0) (Some 6) in
Printf.printf "filter even Some(6): %s\n"
(match even6 with Some n -> string_of_int n | None -> "None")