โข 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
# Python โ None is a valid value anywhere, crashes at runtime
def safe_div(a, b):
if b == 0:
return None # caller might forget to check this!
return a / b
result = safe_div(10, 0)
print(result + 1) # AttributeError: NoneType โ runtime crash
// Rust โ Option forces you to check at compile time
fn safe_div(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
let result = safe_div(10.0, 0.0);
println!("{}", result + 1.0); // COMPILE ERROR: can't use Option<f64> as f64
The Rust version catches the bug before the program ever runs.
// Safe division
fn safe_div(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
// Using Option:
match safe_div(10.0, 2.0) {
Some(result) => println!("{}", result), // 5.0
None => println!("Division by zero"),
}
// Chaining with .map() โ transform the value if it exists
let doubled = safe_div(10.0, 2.0).map(|x| x * 2.0); // Some(10.0)
let nothing = safe_div(10.0, 0.0).map(|x| x * 2.0); // None โ map skips
// Default value
let result = safe_div(10.0, 0.0).unwrap_or(0.0); // 0.0
Result โ for operations that fail with a reason:
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeSquareRoot,
}
fn checked_div(a: i64, b: i64) -> Result<i64, MathError> {
if b == 0 { Err(MathError::DivisionByZero) } else { Ok(a / b) }
}
fn checked_sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 { Err(MathError::NegativeSquareRoot) } else { Ok(x.sqrt()) }
}
The `?` operator โ chain fallible operations cleanly:
// Compute sqrt(a / b) โ two things that can fail
fn sqrt_of_division(a: f64, b: f64) -> Result<f64, MathError> {
let quotient = safe_div(a, b).ok_or(MathError::DivisionByZero)?;
// ^
// If this is Err, return immediately with that error
checked_sqrt(quotient)
// If this is Err, return that error too
}
`?` is Rust's "propagate error upward" operator. It replaces chains of `if err != nil { return err }` in Go, or try/catch in Java. Errors flow up automatically, and the code reads like the happy path.
| Concept | OCaml | Rust | |
|---|---|---|---|
| "Maybe a value" | `'a option` | `Option<T>` | |
| Success/failure | `('a, 'b) result` | `Result<T, E>` | |
| Chaining Options | `Option.map`, `Option.bind` | `.map()`, `.and_then()` | |
| Chaining Results | `Result.bind`, `\ | >` | `.map()`, `.and_then()`, `?` |
| Propagate error | Manual match or `Result.bind` | `?` operator | |
| Force unwrap | `Option.get` (raises) | `.unwrap()` (panics) |
/// Option and Result: safe error handling without exceptions.
///
/// OCaml uses `option` and `result` types. Rust has `Option<T>` and `Result<T, E>`.
/// Both replace null/exceptions with types the compiler forces you to handle.
// โโ Option: safe lookups โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// Find first element matching predicate (idiomatic Rust)
pub fn find_first<T>(list: &[T], pred: impl Fn(&T) -> bool) -> Option<&T> {
list.iter().find(|x| pred(x))
}
/// Safe division โ returns None on divide-by-zero
pub fn safe_div(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
/// Safe head of list
pub fn head<T>(list: &[T]) -> Option<&T> {
list.first()
}
/// Safe last element
pub fn last<T>(list: &[T]) -> Option<&T> {
list.last()
}
// โโ Option combinators (functional chaining) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// Chain optional operations: find element, then transform it
pub fn find_and_double(list: &[i64], pred: impl Fn(&i64) -> bool) -> Option<i64> {
list.iter().find(|x| pred(x)).map(|x| x * 2)
}
/// Get the nth element safely, with a default
pub fn nth_or_default<T: Clone>(list: &[T], n: usize, default: T) -> T {
list.get(n).cloned().unwrap_or(default)
}
// โโ Result: error handling with context โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
#[derive(Debug, PartialEq)]
pub enum MathError {
DivisionByZero,
NegativeSquareRoot,
Overflow,
}
/// Safe division returning Result with error context
pub fn checked_div(a: i64, b: i64) -> Result<i64, MathError> {
if b == 0 {
Err(MathError::DivisionByZero)
} else {
a.checked_div(b).ok_or(MathError::Overflow)
}
}
/// Safe square root
pub fn checked_sqrt(x: f64) -> Result<f64, MathError> {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
/// Chain computations with `?` operator โ Rust's monadic bind
/// Computes: sqrt(a / b)
pub fn sqrt_of_division(a: f64, b: f64) -> Result<f64, MathError> {
let quotient = safe_div(a, b).ok_or(MathError::DivisionByZero)?;
checked_sqrt(quotient)
}
// โโ Recursive style: Option threading โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
/// Recursive lookup in an association list (like OCaml's List.assoc_opt)
pub fn assoc_opt<'a, K: PartialEq, V>(key: &K, pairs: &'a [(K, V)]) -> Option<&'a V> {
match pairs.split_first() {
None => None,
Some(((k, v), _)) if k == key => Some(v),
Some((_, rest)) => assoc_opt(key, rest),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_safe_div() {
assert_eq!(safe_div(10.0, 2.0), Some(5.0));
assert_eq!(safe_div(1.0, 0.0), None);
}
#[test]
fn test_head_last() {
assert_eq!(head(&[1, 2, 3]), Some(&1));
assert_eq!(last(&[1, 2, 3]), Some(&3));
assert_eq!(head::<i32>(&[]), None);
assert_eq!(last::<i32>(&[]), None);
}
#[test]
fn test_find_and_double() {
assert_eq!(find_and_double(&[1, 2, 3, 4], |x| *x > 2), Some(6));
assert_eq!(find_and_double(&[1, 2], |x| *x > 10), None);
}
#[test]
fn test_nth_or_default() {
assert_eq!(nth_or_default(&[10, 20, 30], 1, 0), 20);
assert_eq!(nth_or_default(&[10, 20, 30], 5, 99), 99);
assert_eq!(nth_or_default::<i32>(&[], 0, -1), -1);
}
#[test]
fn test_checked_div() {
assert_eq!(checked_div(10, 2), Ok(5));
assert_eq!(checked_div(10, 0), Err(MathError::DivisionByZero));
}
#[test]
fn test_checked_sqrt() {
assert!((checked_sqrt(4.0).unwrap() - 2.0).abs() < 1e-10);
assert_eq!(checked_sqrt(-1.0), Err(MathError::NegativeSquareRoot));
}
#[test]
fn test_sqrt_of_division_chaining() {
let r = sqrt_of_division(16.0, 4.0).unwrap();
assert!((r - 2.0).abs() < 1e-10);
assert_eq!(sqrt_of_division(16.0, 0.0), Err(MathError::DivisionByZero));
assert_eq!(sqrt_of_division(-16.0, 1.0), Err(MathError::NegativeSquareRoot));
}
#[test]
fn test_assoc_opt() {
let pairs = vec![(1, "one"), (2, "two"), (3, "three")];
assert_eq!(assoc_opt(&2, &pairs), Some(&"two"));
assert_eq!(assoc_opt(&99, &pairs), None);
assert_eq!(assoc_opt::<i32, &str>(&1, &[]), None);
}
}
fn main() {
println!("{:?}", safe_div(10.0, 2.0), Some(5.0));
println!("{:?}", safe_div(1.0, 0.0), None);
println!("{:?}", head(&[1, 2, 3]), Some(&1));
}
(* Option and Result: safe error handling without exceptions *)
(* โโ Option: safe lookups โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ *)
let safe_div a b =
if b = 0.0 then None else Some (a /. b)
let head = function
| [] -> None
| h :: _ -> Some h
let last lst =
let rec aux = function
| [] -> None
| [x] -> Some x
| _ :: t -> aux t
in aux lst
(* โโ Option combinators โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ *)
let find_and_double lst pred =
List.find_opt pred lst |> Option.map (fun x -> x * 2)
let nth_or_default lst n default =
match List.nth_opt lst n with
| Some v -> v
| None -> default
(* โโ Result type (OCaml 4.03+) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ *)
type math_error = Division_by_zero_err | Negative_sqrt | Overflow_err
let checked_div a b =
if b = 0 then Error Division_by_zero_err
else Ok (a / b)
let checked_sqrt x =
if x < 0.0 then Error Negative_sqrt
else Ok (sqrt x)
(* โโ Monadic chaining with Result.bind โโโโโโโโโโโโโโโโโโโโโ *)
let sqrt_of_division a b =
safe_div a b
|> Option.to_result ~none:Division_by_zero_err
|> Result.bind (fun q -> checked_sqrt q)
(* โโ Recursive association list lookup โโโโโโโโโโโโโโโโโโโโโ *)
let rec assoc_opt key = function
| [] -> None
| (k, v) :: _ when k = key -> Some v
| _ :: rest -> assoc_opt key rest
(* โโ Tests โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ *)
let () =
assert (safe_div 10.0 2.0 = Some 5.0);
assert (safe_div 1.0 0.0 = None);
assert (head [1;2;3] = Some 1);
assert (head [] = None);
assert (last [1;2;3] = Some 3);
assert (find_and_double [1;2;3;4] (fun x -> x > 2) = Some 6);
assert (nth_or_default [10;20;30] 1 0 = 20);
assert (nth_or_default [10;20;30] 5 99 = 99);
assert (checked_div 10 2 = Ok 5);
assert (checked_div 10 0 = Error Division_by_zero_err);
assert (assoc_opt 2 [(1,"one");(2,"two")] = Some "two");
assert (assoc_opt 9 [(1,"one")] = None);
print_endline "โ All option/result tests passed"
let safe_div a b =
if b = 0.0 then None else Some (a /. b)
(* Chain with |> and Option.map *)
safe_div 10.0 2.0 |> Option.map (fun x -> x *. x)fn safe_div(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
// The ? operator propagates errors โ Rust's monadic bind
fn sqrt_of_div(a: f64, b: f64) -> Result<f64, MathError> {
let q = safe_div(a, b).ok_or(MathError::DivisionByZero)?;
checked_sqrt(q)
}
Rust has no exceptions โ `Result` is the only way. The `?` operator makes error propagation concise.| Aspect | OCaml | Rust | |
|---|---|---|---|
| Optional values | `'a option` | `Option<T>` | |
| Error handling | `('a, 'b) result` + exceptions | `Result<T, E>` only | |
| Chaining | `\ | >` pipe + `Option.map`/`bind` | `.map()` / `.and_then()` / `?` |
| Error propagation | Manual matching or `Result.bind` | `?` operator (sugar for match) | |
| Unwrapping | `Option.get` (raises) | `.unwrap()` (panics) | |
| Default values | `Option.value ~default:x` | `.unwrap_or(x)` | |
| Conversion | `Option.to_result` | `.ok_or(err)` / `.ok()` |