๐Ÿฆ€ 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

258: Monadic Option Chaining

Difficulty: 2 Level: Intermediate Chain fallible operations so the first `None` short-circuits the entire computation.

The Problem This Solves

Many operations can fail to produce a value: looking up a key in a map, parsing a string, dividing by a number that might be zero, reading a field that might be absent. When you chain several such operations, a `None` at any step should propagate to the end without executing the remaining steps. The naive approach is nested `match` expressions: match on step 1, and inside the `Some` branch, match on step 2, and inside that `Some` branch... Three steps deep and you have three levels of indentation. The structure obscures the logic. The Option monad flattens this: each step either produces a value that flows to the next step, or produces `None` which silently skips everything remaining. This is the same short-circuit logic as `&&` for booleans, lifted to computations that produce values.

The Intuition

Imagine a pipeline of transforms. Each transform says: "If I received a real value, I'll process it and pass along my result. If I received nothing, I'll pass nothing along immediately." The pipeline is written left-to-right; the first failure stops propagation without any if/else at each step. `and_then` is this operation. It's OCaml's `>>=` (bind) for `Option`. `map` is `>>|` โ€” transform the value if present, leave `None` alone. Together they're the two operations of the option monad. Rust's `?` operator is syntactic sugar for the same thing: `expr?` means "unwrap `Some(x)` as `x`, or return `None` immediately". It makes monadic chaining look like sequential imperative code.

How It Works in Rust

pub fn safe_div(x: i32, y: i32) -> Option<i32> {
 if y == 0 { None } else { Some(x / y) }
}

pub fn safe_head(list: &[i32]) -> Option<i32> {
 list.first().copied()
}

// Style 1: and_then (bind) + map โ€” mirrors OCaml's >>= and >>|
pub fn compute(lst: &[i32]) -> Option<i32> {
 safe_head(lst)
     .and_then(|x| safe_div(100, x))  // None if list empty or x == 0
     .map(|r| r * 2)                  // None propagates unchanged
}

// Style 2: ? operator โ€” monadic chaining that reads like imperative code
pub fn compute_q(lst: &[i32]) -> Option<i32> {
 let x = safe_head(lst)?;      // returns None from function if absent
 let r = safe_div(100, x)?;    // returns None if division fails
 Some(r * 2)
}

// Style 3: explicit bind โ€” shows what and_then desugars to
fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
 match opt {
     None => None,
     Some(x) => f(x),  // only calls f if we have a value
 }
}
All three styles produce identical results. `and_then` is composable; `?` is readable; explicit `bind` is educational.

What This Unlocks

Key Differences

ConceptOCamlRust
Bind operatorCustom `>>=` infix`.and_then()` method
Map operatorCustom `>>` infix`.map()` method
Sugar`let*` in `Option` monad context`?` operator
`?` equivalentNone โ€” no direct equivalent`?` desugars to early `return None`
Stdlib bind`Option.bind` (OCaml 4.08+)`and_then` โ€” available from the start
pub fn safe_div(x: i32, y: i32) -> Option<i32> {
    if y == 0 { None } else { Some(x / y) }
}

pub fn safe_head(list: &[i32]) -> Option<i32> {
    list.first().copied()
}

// Idiomatic: Option::and_then (bind) + Option::map (fmap)
pub fn compute_idiomatic(lst: &[i32]) -> Option<i32> {
    safe_head(lst)
        .and_then(|x| safe_div(100, x))
        .map(|r| r * 2)
}

// Explicit bind โ€” shows what and_then desugars to
fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
    match opt {
        None => None,
        Some(x) => f(x),
    }
}

pub fn compute_explicit(lst: &[i32]) -> Option<i32> {
    let divided = bind(safe_head(lst), |x| safe_div(100, x));
    divided.map(|r| r * 2)
}

// Question-mark operator โ€” ergonomic monadic short-circuit
pub fn compute_question_mark(lst: &[i32]) -> Option<i32> {
    let x = safe_head(lst)?;
    let r = safe_div(100, x)?;
    Some(r * 2)
}

fn main() {
    let show = |opt: Option<i32>| match opt {
        None => "None".to_string(),
        Some(x) => x.to_string(),
    };

    println!("--- Idiomatic (and_then + map) ---");
    println!("compute([5,3,1]) = {}", show(compute_idiomatic(&[5, 3, 1])));
    println!("compute([0,1])   = {}", show(compute_idiomatic(&[0, 1])));
    println!("compute([])      = {}", show(compute_idiomatic(&[])));

    println!("\n--- Explicit bind ---");
    println!("compute([5,3,1]) = {}", show(compute_explicit(&[5, 3, 1])));
    println!("compute([0,1])   = {}", show(compute_explicit(&[0, 1])));
    println!("compute([])      = {}", show(compute_explicit(&[])));

    println!("\n--- Question-mark operator ---");
    println!("compute([5,3,1]) = {}", show(compute_question_mark(&[5, 3, 1])));
    println!("compute([0,1])   = {}", show(compute_question_mark(&[0, 1])));
    println!("compute([])      = {}", show(compute_question_mark(&[])));
}

/* Output:
   --- Idiomatic (and_then + map) ---
   compute([5,3,1]) = 40
   compute([0,1])   = None
   compute([])      = None

   --- Explicit bind ---
   compute([5,3,1]) = 40
   compute([0,1])   = None
   compute([])      = None

   --- Question-mark operator ---
   compute([5,3,1]) = 40
   compute([0,1])   = None
   compute([])      = None
*/
(* OCaml option monad: bind and fmap operators *)

(* Monadic bind: >>= propagates None short-circuit *)
let ( >>= ) opt f = match opt with
  | None -> None
  | Some x -> f x

(* Functor map: >>| transforms the value if present *)
let ( >>| ) opt f = match opt with
  | None -> None
  | Some x -> Some (f x)

let safe_div x y = if y = 0 then None else Some (x / y)
let safe_head = function [] -> None | h :: _ -> Some h

(* Idiomatic OCaml: operator chaining *)
let compute lst =
  safe_head lst >>= fun x ->
  safe_div 100 x >>| fun r ->
  r * 2

(* Explicit using Option module โ€” mirrors Rust's and_then + map *)
let compute_explicit lst =
  Option.bind (safe_head lst) (fun x ->
    Option.map (fun r -> r * 2) (safe_div 100 x))

let () =
  let show = function None -> "None" | Some x -> string_of_int x in
  assert (compute [5; 3; 1] = Some 40);
  assert (compute [0; 1] = None);
  assert (compute [] = None);
  assert (compute_explicit [5; 3; 1] = Some 40);
  Printf.printf "%s\n" (show (compute [5; 3; 1]));
  Printf.printf "%s\n" (show (compute [0; 1]));
  Printf.printf "%s\n" (show (compute []));
  print_endline "ok"

๐Ÿ“Š Detailed Comparison

OCaml vs Rust: Monadic Option Chaining

Side-by-Side Code

OCaml

๐Ÿช Show OCaml equivalent
let ( >>= ) opt f = match opt with
| None -> None
| Some x -> f x

let ( >>| ) opt f = match opt with
| None -> None
| Some x -> Some (f x)

let safe_div x y = if y = 0 then None else Some (x / y)
let safe_head = function [] -> None | h :: _ -> Some h

let compute lst =
safe_head lst >>= fun x ->
safe_div 100 x >>| fun r ->
r * 2

Rust (idiomatic)

pub fn safe_div(x: i32, y: i32) -> Option<i32> {
 if y == 0 { None } else { Some(x / y) }
}

pub fn safe_head(list: &[i32]) -> Option<i32> {
 list.first().copied()
}

pub fn compute_idiomatic(lst: &[i32]) -> Option<i32> {
 safe_head(lst)
     .and_then(|x| safe_div(100, x))
     .map(|r| r * 2)
}

Rust (explicit bind โ€” shows the desugaring)

fn bind<T, U>(opt: Option<T>, f: impl FnOnce(T) -> Option<U>) -> Option<U> {
 match opt {
     None => None,
     Some(x) => f(x),
 }
}

pub fn compute_explicit(lst: &[i32]) -> Option<i32> {
 let divided = bind(safe_head(lst), |x| safe_div(100, x));
 divided.map(|r| r * 2)
}

Rust (question-mark operator)

pub fn compute_question_mark(lst: &[i32]) -> Option<i32> {
 let x = safe_head(lst)?;
 let r = safe_div(100, x)?;
 Some(r * 2)
}

Type Signatures

ConceptOCamlRust
Bind operator`val (>>=) : 'a option -> ('a -> 'b option) -> 'b option``fn and_then<U>(self, f: impl FnOnce(T) -> Option<U>) -> Option<U>`
Map operator`val (>>) : 'a option -> ('a -> 'b) -> 'b option``fn map<U>(self, f: impl FnOnce(T) -> U) -> Option<U>`
Safe division`val safe_div : int -> int -> int option``fn safe_div(x: i32, y: i32) -> Option<i32>`
Safe head`val safe_head : 'a list -> 'a option``fn safe_head(list: &[i32]) -> Option<i32>`

Key Insights

1. `>>=` is `and_then`: OCaml's custom bind operator and Rust's `Option::and_then` are identical in semantics โ€” both propagate `None` and apply `f` to the inner value when `Some`. Rust provides this in the standard library; OCaml developers historically defined it themselves.

2. `>>|` is `Option::map`: The functor-map operator in OCaml is exactly `Option::map` in Rust. Clippy will even reject a manual Rust implementation of `fmap` with `match`, telling you to use `.map()` instead โ€” confirming this identity.

3. The `?` operator desugars to bind: Rust's `?` on an `Option` is syntactic sugar for "return `None` early if `None`, otherwise unwrap". This is monadic short-circuit with imperative-style syntax, unique to Rust and without a direct OCaml equivalent.

4. Value ownership vs. reference semantics: Rust's `and_then` and `map` consume the `Option` by value, and the closures receive owned `T`. OCaml also passes values, but without explicit ownership tracking. In Rust, using `.copied()` on `Option<&T>` to produce `Option<T>` is the idiomatic way to decouple borrowing from the chain.

5. No user-defined infix operators in stable Rust: OCaml makes it natural to define `>>=` as an infix operator. Rust does not support custom infix operators in stable code, so the chaining reads as method calls. This is a deliberate Rust design decision for readability and tooling.

When to Use Each Style

Use `and_then` + `map` when: building a pipeline with multiple fallible steps that reads cleanly left-to-right โ€” the method chain style makes the data flow obvious and composes well with iterator chains.

Use `?` when: writing code that resembles sequential imperative steps, or when intermediate values need to be named and reused. The `?` style is easier for developers unfamiliar with monadic thinking to read and debug.

Use explicit `bind` (match) when: teaching or documenting what monadic chaining means under the hood, or when porting OCaml code directly for comparison purposes.