• 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
cargo test
// 004: Option and Result
// Safe handling of missing values and errors
// Approach 1: Option basics
fn safe_div(a: i32, b: i32) -> Option<i32> {
if b == 0 { None } else { Some(a / b) }
}
fn safe_head(v: &[i32]) -> Option<i32> {
v.first().copied()
}
fn find_even(v: &[i32]) -> Option<i32> {
v.iter().find(|&&x| x % 2 == 0).copied()
}
// Approach 2: Chaining with map and and_then
fn double_head(v: &[i32]) -> Option<i32> {
safe_head(v).map(|x| x * 2)
}
fn safe_div_then_add(a: i32, b: i32, c: i32) -> Option<i32> {
safe_div(a, b).map(|q| q + c)
}
fn chain_lookups(v1: &[i32], v2: &[i32]) -> Option<i32> {
safe_head(v1).and_then(|idx| v2.get(idx as usize).copied())
}
// Approach 3: Result for richer errors
#[derive(Debug, PartialEq)]
enum MyError {
DivByZero,
NegativeInput,
EmptyList,
}
fn safe_div_r(a: i32, b: i32) -> Result<i32, MyError> {
if b == 0 { Err(MyError::DivByZero) } else { Ok(a / b) }
}
fn safe_sqrt(x: f64) -> Result<f64, MyError> {
if x < 0.0 { Err(MyError::NegativeInput) } else { Ok(x.sqrt()) }
}
fn safe_head_r(v: &[i32]) -> Result<i32, MyError> {
v.first().copied().ok_or(MyError::EmptyList)
}
fn compute(v: &[i32]) -> Result<i32, MyError> {
let x = safe_head_r(v)?;
safe_div_r(x * 10, 3)
}
fn main() {
println!("safe_div(10, 3) = {:?}", safe_div(10, 3));
println!("safe_div(10, 0) = {:?}", safe_div(10, 0));
println!("compute([5, 10]) = {:?}", compute(&[5, 10]));
println!("compute([]) = {:?}", compute(&[]));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_div() {
assert_eq!(safe_div(10, 3), Some(3));
assert_eq!(safe_div(10, 0), None);
}
#[test]
fn test_safe_head() {
assert_eq!(safe_head(&[1, 2, 3]), Some(1));
assert_eq!(safe_head(&[]), None);
}
#[test]
fn test_find_even() {
assert_eq!(find_even(&[1, 3, 4, 5]), Some(4));
assert_eq!(find_even(&[1, 3, 5]), None);
}
#[test]
fn test_double_head() {
assert_eq!(double_head(&[5, 10]), Some(10));
assert_eq!(double_head(&[]), None);
}
#[test]
fn test_chain_lookups() {
assert_eq!(chain_lookups(&[1], &[10, 20, 30]), Some(20));
assert_eq!(chain_lookups(&[], &[10, 20]), None);
}
#[test]
fn test_result() {
assert_eq!(safe_div_r(10, 2), Ok(5));
assert_eq!(safe_div_r(10, 0), Err(MyError::DivByZero));
}
#[test]
fn test_compute() {
assert_eq!(compute(&[5, 10]), Ok(16));
assert_eq!(compute(&[]), Err(MyError::EmptyList));
}
}
(* 004: Option and Result *)
(* Safe handling of missing values and errors *)
(* Approach 1: Option basics *)
let safe_div a b =
if b = 0 then None else Some (a / b)
let safe_head = function
| [] -> None
| x :: _ -> Some x
let find_even lst =
List.find_opt (fun x -> x mod 2 = 0) lst
(* Approach 2: Chaining with map and bind *)
let double_head lst =
safe_head lst |> Option.map (fun x -> x * 2)
let safe_div_then_add a b c =
safe_div a b |> Option.map (fun q -> q + c)
let chain_lookups lst1 lst2 =
safe_head lst1
|> Option.bind (fun idx ->
if idx >= 0 && idx < List.length lst2
then Some (List.nth lst2 idx)
else None)
(* Approach 3: Result for richer errors *)
type error = DivByZero | NegativeInput | EmptyList
let safe_div_r a b =
if b = 0 then Error DivByZero else Ok (a / b)
let safe_sqrt x =
if x < 0.0 then Error NegativeInput
else Ok (sqrt x)
let safe_head_r = function
| [] -> Error EmptyList
| x :: _ -> Ok x
let compute lst =
match safe_head_r lst with
| Error e -> Error e
| Ok x -> safe_div_r (x * 10) 3
(* Tests *)
let () =
assert (safe_div 10 3 = Some 3);
assert (safe_div 10 0 = None);
assert (safe_head [1; 2; 3] = Some 1);
assert (safe_head [] = None);
assert (double_head [5; 10] = Some 10);
assert (double_head [] = None);
assert (safe_div_then_add 10 3 5 = Some 8);
assert (safe_div_then_add 10 0 5 = None);
assert (chain_lookups [1] [10; 20; 30] = Some 20);
assert (chain_lookups [] [10; 20] = None);
assert (safe_div_r 10 2 = Ok 5);
assert (safe_div_r 10 0 = Error DivByZero);
assert (compute [5; 10] = Ok 16);
assert (compute [] = Error EmptyList);
Printf.printf "✓ All tests passed\n"
`Option` replaces null pointers. `Result` replaces exceptions. Both languages encode success/failure in the type system, forcing the caller to handle every case.
| Feature | OCaml | Rust |
|---|---|---|
| Option type | `'a option` | `Option<T>` |
| Result type | `('a, 'e) result` | `Result<T, E>` |
| Map | `Option.map f o` | `o.map(f)` |
| Bind/FlatMap | `Option.bind o f` | `o.and_then(f)` |
| Default | `Option.value ~default o` | `o.unwrap_or(d)` |
| Error propagation | Pattern match | `?` operator |