🦀 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

259: Result Monad — Error Chaining

Difficulty: 2 Level: Intermediate Chain validation steps so the first failure short-circuits with a descriptive error — railway-oriented programming.

The Problem This Solves

Input validation often has multiple steps: parse the string as an integer, check it's positive, check it's even. Each step can succeed or fail with a different error message. Nesting `match` expressions for each step creates a pyramid of indentation and obscures the happy path. Railway-oriented programming offers a cleaner mental model: there are two tracks, the success track and the error track. Each validation step is a switch. If the value on the success track passes the check, it continues forward. If it fails, it's diverted to the error track. Once on the error track, all remaining steps are skipped automatically — the error just travels to the end. `Result<T, E>` is Rust's railway: `Ok(T)` is on the success track, `Err(E)` is on the error track. `and_then` is the switch. The `?` operator is syntactic sugar for the same switching, letting you write the happy path sequentially.

The Intuition

Think of `Result` as a train on two parallel tracks. `Ok(value)` means the train is on the success track carrying `value`. `Err(msg)` means the train has switched to the error track carrying `msg`. `and_then` says: "if on success track, apply this function; if on error track, stay there and skip". Chain three `and_then` calls and you get three switches — each one either continues the journey or terminates it with an error. The `?` operator makes this invisible: `f()?` means "if `Err`, return it immediately from this function; if `Ok(x)`, give me `x`". Writing three `?`-terminated calls in sequence gives you the railway structure with imperative readability.

How It Works in Rust

pub fn parse_int(s: &str) -> Result<i64, String> {
 s.parse::<i64>().map_err(|_| format!("Not an integer: {s}"))
}

pub fn check_positive(n: i64) -> Result<i64, String> {
 if n > 0 { Ok(n) } else { Err("Must be positive".to_string()) }
}

pub fn check_even(n: i64) -> Result<i64, String> {
 if n % 2 == 0 { Ok(n) } else { Err("Must be even".to_string()) }
}

// Style 1: and_then chain — mirrors OCaml's >>= operator exactly
pub fn validate(s: &str) -> Result<i64, String> {
 parse_int(s).and_then(check_positive).and_then(check_even)
}

// Style 2: ? operator — monadic sequencing with imperative style
pub fn validate_q(s: &str) -> Result<i64, String> {
 let n = parse_int(s)?;       // Err propagates immediately
 let n = check_positive(n)?;  // skipped if previous returned Err
 let n = check_even(n)?;
 Ok(n)
}

// Style 3: explicit bind — shows what and_then desugars to
fn bind<T, U, E>(r: Result<T, E>, f: impl FnOnce(T) -> Result<U, E>) -> Result<U, E> {
 match r {
     Err(e) => Err(e),  // error track: skip f, propagate error
     Ok(v) => f(v),     // success track: apply f
 }
}
`map_err` converts the `ParseIntError` from `str::parse` into our uniform `String` error type — a necessary step when combining functions with different error types.

What This Unlocks

Key Differences

ConceptOCamlRust
Bind operatorCustom `>>=` infix on `result``.and_then()` method — no custom operator
`?` equivalentNone`?` desugars to early `return Err(e)`
Error conversionString concatenation`map_err(\_\...)` converts error types
Error type`string` for all stepsGeneric `E` — same type across the chain
Syntax sugar`let*` (bind notation, OCaml 4.08+)`?` — idiomatic in all Rust code
// Solution 1: Idiomatic Rust — Result's built-in monadic combinator
// `and_then` is Rust's bind (>>=) for Result: propagates Err, applies f to Ok
pub fn parse_int(s: &str) -> Result<i64, String> {
    s.parse::<i64>().map_err(|_| format!("Not an integer: {s}"))
}

pub fn check_positive(n: i64) -> Result<i64, String> {
    if n > 0 {
        Ok(n)
    } else {
        Err("Must be positive".to_string())
    }
}

pub fn check_even(n: i64) -> Result<i64, String> {
    if n % 2 == 0 {
        Ok(n)
    } else {
        Err("Must be even".to_string())
    }
}

// Railway-oriented: each step either advances the train or diverts to the error track
pub fn validate_idiomatic(s: &str) -> Result<i64, String> {
    parse_int(s).and_then(check_positive).and_then(check_even)
}

// Solution 2: Explicit bind — mirrors OCaml's >>= operator exactly
fn bind<T, U, E>(r: Result<T, E>, f: impl FnOnce(T) -> Result<U, E>) -> Result<U, E> {
    match r {
        Err(e) => Err(e),
        Ok(x) => f(x),
    }
}

pub fn validate_explicit(s: &str) -> Result<i64, String> {
    bind(bind(parse_int(s), check_positive), check_even)
}

// Solution 3: Using the `?` operator — Rust's ergonomic monadic shorthand
pub fn validate_question_mark(s: &str) -> Result<i64, String> {
    let n = parse_int(s)?;
    let n = check_positive(n)?;
    check_even(n)
}

fn main() {
    let inputs = ["42", "-3", "abc", "7"];

    println!("=== validate_idiomatic (and_then chain) ===");
    for s in &inputs {
        match validate_idiomatic(s) {
            Ok(n) => println!("{s} -> Ok {n}"),
            Err(e) => println!("{s} -> Error: {e}"),
        }
    }

    println!("\n=== validate_question_mark (? operator) ===");
    for s in &inputs {
        match validate_question_mark(s) {
            Ok(n) => println!("{s} -> Ok {n}"),
            Err(e) => println!("{s} -> Error: {e}"),
        }
    }

    println!("\n=== validate_explicit (explicit bind) ===");
    for s in &inputs {
        match validate_explicit(s) {
            Ok(n) => println!("{s} -> Ok {n}"),
            Err(e) => println!("{s} -> Error: {e}"),
        }
    }
}

/* Output:
   === validate_idiomatic (and_then chain) ===
   42 -> Ok 42
   -3 -> Error: Must be positive
   abc -> Error: Not an integer: abc
   7 -> Error: Must be even

   === validate_question_mark (? operator) ===
   42 -> Ok 42
   -3 -> Error: Must be positive
   abc -> Error: Not an integer: abc
   7 -> Error: Must be even

   === validate_explicit (explicit bind) ===
   42 -> Ok 42
   -3 -> Error: Must be positive
   abc -> Error: Not an integer: abc
   7 -> Error: Must be even
*/

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

    #[test]
    fn test_valid_positive_even_succeeds() {
        assert_eq!(validate_idiomatic("42"), Ok(42));
        assert_eq!(validate_explicit("42"), Ok(42));
        assert_eq!(validate_question_mark("42"), Ok(42));
    }

    #[test]
    fn test_negative_number_fails_check_positive() {
        let expected = Err("Must be positive".to_string());
        assert_eq!(validate_idiomatic("-3"), expected);
        assert_eq!(validate_explicit("-3"), expected);
        assert_eq!(validate_question_mark("-3"), expected);
    }

    #[test]
    fn test_non_integer_string_fails_parse() {
        let expected = Err("Not an integer: abc".to_string());
        assert_eq!(validate_idiomatic("abc"), expected);
        assert_eq!(validate_explicit("abc"), expected);
        assert_eq!(validate_question_mark("abc"), expected);
    }

    #[test]
    fn test_positive_odd_fails_check_even() {
        let expected = Err("Must be even".to_string());
        assert_eq!(validate_idiomatic("7"), expected);
        assert_eq!(validate_explicit("7"), expected);
        assert_eq!(validate_question_mark("7"), expected);
    }

    #[test]
    fn test_zero_fails_check_positive() {
        let expected = Err("Must be positive".to_string());
        assert_eq!(validate_idiomatic("0"), expected);
        assert_eq!(validate_explicit("0"), expected);
        assert_eq!(validate_question_mark("0"), expected);
    }

    #[test]
    fn test_parse_int_valid() {
        assert_eq!(parse_int("100"), Ok(100));
        assert_eq!(parse_int("-5"), Ok(-5));
        assert_eq!(parse_int("0"), Ok(0));
    }

    #[test]
    fn test_parse_int_invalid() {
        assert!(parse_int("abc").is_err());
        assert!(parse_int("1.5").is_err());
        assert!(parse_int("").is_err());
    }

    #[test]
    fn test_error_stops_at_first_failure() {
        let result = validate_idiomatic("abc");
        assert!(result.unwrap_err().contains("Not an integer"));
    }
}
(* OCaml >>= for Result: short-circuit on Error, apply f to Ok *)
let ( >>= ) r f = match r with
  | Error _ as e -> e
  | Ok x -> f x

(* Idiomatic: parse string to int, returning Result *)
let parse_int s =
  match int_of_string_opt s with
  | Some n -> Ok n
  | None -> Error ("Not an integer: " ^ s)

let check_positive n =
  if n > 0 then Ok n else Error "Must be positive"

let check_even n =
  if n mod 2 = 0 then Ok n else Error "Must be even"

(* Railway-oriented: chain validations with >>= *)
let validate s =
  parse_int s >>= check_positive >>= check_even

(* Recursive-style: explicit function composition via bind *)
let rec bind r f = match r with
  | Error _ as e -> e
  | Ok x -> f x

let validate_explicit s =
  bind (bind (parse_int s) check_positive) check_even

let () =
  assert (validate "42" = Ok 42);
  assert (validate "-3" = Error "Must be positive");
  assert (validate "abc" = Error "Not an integer: abc");
  assert (validate "7" = Error "Must be even");
  assert (validate "0" = Error "Must be positive");
  assert (validate_explicit "42" = Ok 42);
  assert (validate_explicit "-3" = Error "Must be positive");
  List.iter (fun s ->
    match validate s with
    | Ok n -> Printf.printf "%s -> Ok %d\n" s n
    | Error e -> Printf.printf "%s -> Error: %s\n" s e
  ) ["42"; "-3"; "abc"; "7"];
  print_endline "ok"

📊 Detailed Comparison

OCaml vs Rust: Result Monad — Error Chaining

Side-by-Side Code

OCaml

🐪 Show OCaml equivalent
let ( >>= ) r f = match r with
| Error _ as e -> e
| Ok x -> f x

let parse_int s =
match int_of_string_opt s with
| Some n -> Ok n
| None -> Error ("Not an integer: " ^ s)

let check_positive n =
if n > 0 then Ok n else Error "Must be positive"

let check_even n =
if n mod 2 = 0 then Ok n else Error "Must be even"

let validate s =
parse_int s >>= check_positive >>= check_even

Rust (idiomatic — and_then chain)

pub fn parse_int(s: &str) -> Result<i64, String> {
 s.parse::<i64>().map_err(|_| format!("Not an integer: {s}"))
}

pub fn check_positive(n: i64) -> Result<i64, String> {
 if n > 0 { Ok(n) } else { Err("Must be positive".to_string()) }
}

pub fn check_even(n: i64) -> Result<i64, String> {
 if n % 2 == 0 { Ok(n) } else { Err("Must be even".to_string()) }
}

pub fn validate_idiomatic(s: &str) -> Result<i64, String> {
 parse_int(s).and_then(check_positive).and_then(check_even)
}

Rust (? operator — sequential style)

pub fn validate_question_mark(s: &str) -> Result<i64, String> {
 let n = parse_int(s)?;
 let n = check_positive(n)?;
 check_even(n)
}

Rust (explicit bind — mirrors OCaml's >>= directly)

fn bind<T, U, E>(r: Result<T, E>, f: impl FnOnce(T) -> Result<U, E>) -> Result<U, E> {
 match r {
     Err(e) => Err(e),
     Ok(x) => f(x),
 }
}

pub fn validate_explicit(s: &str) -> Result<i64, String> {
 bind(bind(parse_int(s), check_positive), check_even)
}

Type Signatures

ConceptOCamlRust
Result type`('a, 'b) result``Result<T, E>`
Bind operator`val (>>=) : ('a,'e) result -> ('a -> ('b,'e) result) -> ('b,'e) result``fn and_then<U, F>(self, f: F) -> Result<U, E>`
Validate signature`val validate : string -> (int, string) result``fn validate(s: &str) -> Result<i64, String>`
Error conversion`"Not an integer: " ^ s` (string concat)`format!("Not an integer: {s}")` + `map_err`
Short-circuit`Error _ as e -> e` in `>>=`Implicit in `and_then` / early return via `?`

Key Insights

1. `and_then` is `>>=`: Rust's `Result::and_then` is the stdlib bind combinator — no custom operator needed. It unwraps `Ok` and passes the value to the next function, or passes `Err` through unchanged.

2. `?` is do-notation sugar: The `?` operator desugars to a match on `Result` that early-returns `Err`. Sequential `?` usage gives the same left-to-right chaining as `>>=` but looks like ordinary imperative code.

3. Error type uniformity: OCaml's polymorphic `result` lets you mix error types freely. Rust requires all steps in a chain to share the same `E` — here `String`. `map_err` is the idiomatic adapter when upstream errors differ.

4. First failure wins: In both languages the chain stops at the first `Err`/`Error`. `parse_int "abc"` never reaches `check_positive` or `check_even`; the error message reflects exactly which step failed.

5. Railway metaphor: Each validation function is a railway switch — the "success track" (`Ok`) continues forward; the "error track" (`Err`) bypasses all remaining steps and exits at the end. This pattern makes error handling compositional and centralized.

When to Use Each Style

Use `and_then` chain when: The validators are already written as standalone functions and you want a point-free, functional pipeline that reads like a sentence.

Use `?` operator when: The validation logic is complex, needs intermediate let-bindings, or benefits from the familiar sequential look — particularly inside a function body with other logic.

Use explicit `bind` when: You are teaching the monad concept or need to abstract over multiple monadic types (e.g., building a generic combinator library).