๐Ÿฆ€ Functional Rust
๐ŸŽฌ Error Handling in Rust Option, Result, the ? operator, and combinators.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Option represents a value that may or may not exist โ€” Some(value) or None

โ€ข Result represents success (Ok) or failure (Err) โ€” no exceptions needed

โ€ข 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

041: Option Basics

Difficulty: 1 Level: Foundations Meet `Option<T>` โ€” Rust's way of saying "this value might not exist", with no nulls allowed.

The Problem This Solves

Every language has the "might be empty" problem: dividing by zero, getting the first element of an empty list, looking up a key that might not exist. Most languages solve this with `null` or `None` โ€” and then silently crash at runtime when you forget to check. Python's `None`, JavaScript's `null`/`undefined`, Java's `null` โ€” all the same trap: the absence of a value looks like a normal value until it explodes. Tony Hoare called null "my billion-dollar mistake." Rust has no null. Instead, `Option<T>` is a type that's either `Some(value)` or `None`. The compiler sees these as completely different cases. You cannot use an `Option<T>` as if it were a `T` โ€” you must explicitly handle both cases. The error happens at compile time, not at 3am on production.

The Intuition

In Python: `Optional[int]` from the `typing` module. But Python's type hints are advisory โ€” the runtime doesn't enforce them. You can still pass `None` where an `int` was expected and only find out when the code runs. In Rust, `Option<T>` is not a hint โ€” it's the actual type. There is no `T` until you unwrap the `Option`. The two cases:
Some(42)  โ€” there IS a value, and it's 42
None      โ€” there is NO value
This is equivalent to a tagged union (discriminated union in TypeScript, `Maybe` in Haskell, `Optional<T>` in Java 8+). The difference: Rust enforces it at compile time.

How It Works in Rust

// Safe division: instead of panicking on /0, return None
fn safe_div(a: i64, b: i64) -> Option<i64> {
 if b == 0 { None } else { Some(a / b) }
}

// Safe head: instead of panicking on empty slice, return None
fn head<T: Clone>(lst: &[T]) -> Option<T> {
 lst.first().cloned()
}
Using the result โ€” three common patterns:
// 1. Pattern match (explicit, exhaustive)
match safe_div(10, 2) {
 Some(result) => println!("Got: {}", result),
 None => println!("Division by zero"),
}

// 2. unwrap_or (provide a default)
let x = safe_div(10, 0).unwrap_or(0);  // x = 0

// 3. The ? operator (propagate None upward)
fn compute() -> Option<i64> {
 let x = safe_div(100, 5)?;  // if None, return None from compute()
 let y = safe_div(x, 2)?;
 Some(y + 1)
}
Never do this in production: `option.unwrap()` โ€” it panics on `None`. Use `unwrap_or`, `unwrap_or_else`, or `?` instead.

What This Unlocks

Key Differences

ConceptOCamlRust
The type`'a option``Option<T>`
Present value`Some x``Some(x)`
Absent value`None``None`
Pattern match`match opt with \Some x -> ... \None -> ...``match opt { Some(x) => ..., None => ... }`
Default value`Option.value opt ~default:0``opt.unwrap_or(0)`
Propagate None`Option.bind` or `let*``?` operator
// Option Basics โ€” 99 Problems #41
// Explore Option<T>: construction, pattern matching, unwrapping.

/// Safe division โ€” returns None on division by zero.
fn safe_div(a: i64, b: i64) -> Option<i64> {
    if b == 0 { None } else { Some(a / b) }
}

/// Safe head of a list.
fn head<T: Clone>(lst: &[T]) -> Option<T> {
    lst.first().cloned()
}

/// Safe last of a list.
fn last<T: Clone>(lst: &[T]) -> Option<T> {
    lst.last().cloned()
}

/// Find first element satisfying predicate.
fn find<T: Clone, F: Fn(&T) -> bool>(lst: &[T], pred: F) -> Option<T> {
    lst.iter().find(|x| pred(x)).cloned()
}

/// Combine two Options โ€” both must be Some.
fn zip_options<A: Clone, B: Clone>(a: Option<A>, b: Option<B>) -> Option<(A, B)> {
    match (a, b) {
        (Some(x), Some(y)) => Some((x, y)),
        _ => None,
    }
}

fn main() {
    println!("safe_div(10, 2)  = {:?}", safe_div(10, 2));
    println!("safe_div(10, 0)  = {:?}", safe_div(10, 0));

    let nums = vec![1, 2, 3, 4, 5];
    println!("head([1..5])     = {:?}", head(&nums));
    println!("last([1..5])     = {:?}", last(&nums));
    println!("head([])         = {:?}", head::<i32>(&[]));

    println!("find >3          = {:?}", find(&nums, |&x| x > 3));
    println!("find >99         = {:?}", find(&nums, |&x| x > 99));

    // unwrap_or
    println!("div(10,0).unwrap_or(0) = {}", safe_div(10, 0).unwrap_or(0));

    // ? operator style via closure
    let compute = || -> Option<i64> {
        let x = safe_div(100, 5)?;
        let y = safe_div(x, 2)?;
        Some(y + 1)
    };
    println!("compute()        = {:?}", compute());

    println!("zip(Some(1),Some('a')) = {:?}", zip_options(Some(1), Some('a')));
    println!("zip(None,Some('a'))    = {:?}", zip_options::<i32,char>(None, Some('a')));
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_safe_div() {
        assert_eq!(safe_div(10, 2), Some(5));
        assert_eq!(safe_div(10, 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);
    }

    #[test]
    fn test_find() {
        assert_eq!(find(&[1, 2, 3, 4], |&x| x > 2), Some(3));
        assert_eq!(find(&[1, 2, 3], |&x| x > 99), None);
    }

    #[test]
    fn test_zip_options() {
        assert_eq!(zip_options(Some(1), Some("hello")), Some((1, "hello")));
        assert_eq!(zip_options::<i32, &str>(None, Some("x")), None);
    }
}
(* Option Basics *)
(* OCaml 99 Problems #41 *)

(* Implementation for example 41 *)

(* Tests *)
let () =
  (* Add tests *)
  print_endline "โœ“ OCaml tests passed"