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

056: Result Monad

Difficulty: โญโญ Level: Intermediate Chain fallible operations on `Result` โ€” getting typed error information at every failure point, with zero nesting.

The Problem This Solves

You have a sequence of steps where each can fail, and you care why it failed, not just that it failed. Maybe you're parsing user input, then validating it, then processing it. Each step uses `Result<T, E>` to carry an error message or error type when it goes wrong. The naive version is a cascade of matches:
// Without and_then: nesting grows with each step
fn validate_input(s: &str) -> Result<i32, String> {
 match s.parse::<i32>() {
     Err(_) => Err(format!("Not an integer: {}", s)),
     Ok(n) => {
         if n <= 0 {
             Err(format!("Not positive: {}", n))
         } else {
             match n % 2 {
                 1 => Err(format!("Not even: {}", n)),
                 _ => Ok(n),
             }
         }
     }
 }
}
Every step adds another level of indentation. The happy path runs down the rightmost column. Error handling dominates the structure. If you add a fourth validation step, you nest again. The real pain: `Err` propagates identically through every step โ€” if any step fails, the whole chain stops and carries that error forward. You're writing the same pattern repeatedly to express a simple idea: "do this, then this, then this, and if anything fails, stop and tell me why." The Result monad exists to solve exactly that pain.

The Intuition

Think of a quality control line in a factory. Raw material goes in, finished product (or rejection slip) comes out. Each inspector either passes the item along or stamps it with a specific reason for rejection and sends it to the reject bin. Once rejected, no further inspectors touch it โ€” the rejection slip travels all the way to the end unchanged. `Result` is that quality control line. `Ok(value)` is "passes inspection." `Err(reason)` is the rejection slip. Once you get `Err`, every subsequent step is skipped and that exact error comes out the end. `and_then` connects inspectors: "if the item passed the previous station, pass it to this function; otherwise, let the rejection slip through unchanged."
// Three inspectors, connected
parse_int(s)              // Station 1: is it a number?
 .and_then(check_positive)  // Station 2: is it positive?
 .and_then(check_even)      // Station 3: is it even?
If station 1 fails with `Err("Not an integer: foo")`, stations 2 and 3 never run. The error arrives at the end exactly as produced. This is a monad: a pattern for chaining operations that carry failure context, without nesting. And again โ€” `?` is the same thing written as early return.

How It Works in Rust

Three validators, each returning Result
fn parse_int(s: &str) -> Result<i32, String> {
 s.parse::<i32>().map_err(|_| format!("Not an integer: {}", s))
 // map_err converts parse's error type into our String error
}

fn check_positive(n: i32) -> Result<i32, String> {
 if n > 0 { Ok(n) } else { Err(format!("Not positive: {}", n)) }
}

fn check_even(n: i32) -> Result<i32, String> {
 if n % 2 == 0 { Ok(n) } else { Err(format!("Not even: {}", n)) }
}
Chain them with `and_then`
fn validate_input(s: &str) -> Result<i32, String> {
 parse_int(s)
     .and_then(check_positive)  // only runs if parse_int returned Ok
     .and_then(check_even)      // only runs if check_positive returned Ok
}

validate_input("42")     // Ok(42)
validate_input("hello")  // Err("Not an integer: hello")
validate_input("-4")     // Err("Not positive: -4")
validate_input("7")      // Err("Not even: 7")
The same chain with `?`
fn validate_input(s: &str) -> Result<i32, String> {
 let n = parse_int(s)?;      // returns Err early if this fails
 let n = check_positive(n)?; // returns Err early if this fails
 let n = check_even(n)?;     // returns Err early if this fails
 Ok(n)
}
Identical behavior, different style. `?` is cleaner when steps have names; `.and_then()` is cleaner for one-liners or lambdas. Typed errors (the idiomatic Rust upgrade) When you want the caller to distinguish error cases at compile time, use an enum instead of `String`:
#[derive(Debug, PartialEq)]
enum ValidationError {
 ParseError(String),
 NotPositive(i32),
 NotEven(i32),
}

fn validate_typed(s: &str) -> Result<i32, ValidationError> {
 let n = parse_int_typed(s)?;       // Err(ValidationError::ParseError(...))
 let n = check_positive_typed(n)?;  // Err(ValidationError::NotPositive(...))
 check_even_typed(n)                // Err(ValidationError::NotEven(...))
}
Now the caller can `match` on the exact variant โ€” no string parsing needed to figure out what went wrong.

What This Unlocks

Key Differences

ConceptOCamlRust
Monadic bind`Result.bind` / `>>=` operator`Result::and_then`
Do-notation sugarNot built in`?` operator
Error typesStrings or polymorphic variants commonCustom error enums idiomatic
Error conversionExplicit mapping required`From` trait + `?` auto-converts
Short-circuit`Err` propagates through bind`Err` propagates through `and_then` / `?`
// Example 056: Result Monad
// Result monad: chain computations that may fail with error info

// Approach 1: and_then chains
fn parse_int(s: &str) -> Result<i32, String> {
    s.parse::<i32>().map_err(|_| format!("Not an integer: {}", s))
}

fn check_positive(n: i32) -> Result<i32, String> {
    if n > 0 { Ok(n) } else { Err(format!("Not positive: {}", n)) }
}

fn check_even(n: i32) -> Result<i32, String> {
    if n % 2 == 0 { Ok(n) } else { Err(format!("Not even: {}", n)) }
}

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

// Approach 2: Using ? operator (Rust's monadic do-notation)
fn validate_input_question(s: &str) -> Result<i32, String> {
    let n = parse_int(s)?;
    let n = check_positive(n)?;
    let n = check_even(n)?;
    Ok(n)
}

// Approach 3: Map and bind combined
fn double_validated(s: &str) -> Result<i32, String> {
    validate_input(s).map(|n| n * 2)
}

// Bonus: custom error type with From for automatic ? conversion
#[derive(Debug, PartialEq)]
enum ValidationError {
    ParseError(String),
    NotPositive(i32),
    NotEven(i32),
}

fn parse_int_typed(s: &str) -> Result<i32, ValidationError> {
    s.parse::<i32>().map_err(|_| ValidationError::ParseError(s.to_string()))
}

fn check_positive_typed(n: i32) -> Result<i32, ValidationError> {
    if n > 0 { Ok(n) } else { Err(ValidationError::NotPositive(n)) }
}

fn check_even_typed(n: i32) -> Result<i32, ValidationError> {
    if n % 2 == 0 { Ok(n) } else { Err(ValidationError::NotEven(n)) }
}

fn validate_typed(s: &str) -> Result<i32, ValidationError> {
    let n = parse_int_typed(s)?;
    let n = check_positive_typed(n)?;
    check_even_typed(n)
}

fn main() {
    println!("validate '42': {:?}", validate_input("42"));
    println!("validate 'hello': {:?}", validate_input("hello"));
    println!("validate '-4': {:?}", validate_input("-4"));
    println!("validate '7': {:?}", validate_input("7"));
    println!("double '42': {:?}", double_validated("42"));
    println!("typed '42': {:?}", validate_typed("42"));
}

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

    #[test]
    fn test_valid_input() {
        assert_eq!(validate_input("42"), Ok(42));
    }

    #[test]
    fn test_parse_error() {
        assert_eq!(validate_input("hello"), Err("Not an integer: hello".into()));
    }

    #[test]
    fn test_not_positive() {
        assert_eq!(validate_input("-4"), Err("Not positive: -4".into()));
    }

    #[test]
    fn test_not_even() {
        assert_eq!(validate_input("7"), Err("Not even: 7".into()));
    }

    #[test]
    fn test_question_mark_same_as_and_then() {
        for s in &["42", "hello", "-4", "7"] {
            assert_eq!(validate_input(s), validate_input_question(s));
        }
    }

    #[test]
    fn test_double() {
        assert_eq!(double_validated("42"), Ok(84));
    }

    #[test]
    fn test_typed_errors() {
        assert_eq!(validate_typed("42"), Ok(42));
        assert_eq!(validate_typed("bad"), Err(ValidationError::ParseError("bad".into())));
        assert_eq!(validate_typed("-2"), Err(ValidationError::NotPositive(-2)));
        assert_eq!(validate_typed("3"), Err(ValidationError::NotEven(3)));
    }
}
(* Example 056: Result Monad *)
(* Result monad: chain computations that may fail with error info *)

let bind r f = match r with Error e -> Error e | Ok x -> f x
let ( >>= ) = bind
let return_ x = Ok x

(* Approach 1: Parsing pipeline *)
let parse_int s =
  match int_of_string_opt s with
  | Some n -> Ok n
  | None -> Error (Printf.sprintf "Not an integer: %s" s)

let check_positive n =
  if n > 0 then Ok n
  else Error (Printf.sprintf "Not positive: %d" n)

let check_even n =
  if n mod 2 = 0 then Ok n
  else Error (Printf.sprintf "Not even: %d" n)

let validate_input s =
  parse_int s >>= check_positive >>= check_even

(* Approach 2: Using Result.bind from stdlib *)
let validate_input_stdlib s =
  Result.bind (parse_int s) (fun n ->
  Result.bind (check_positive n) (fun n ->
  check_even n))

(* Approach 3: Map and bind combined *)
let double_validated s =
  validate_input s |> Result.map (fun n -> n * 2)

let () =
  assert (validate_input "42" = Ok 42);
  assert (validate_input "hello" = Error "Not an integer: hello");
  assert (validate_input "-4" = Error "Not positive: -4");
  assert (validate_input "7" = Error "Not even: 7");
  assert (double_validated "42" = Ok 84);

  (* Stdlib version *)
  assert (validate_input_stdlib "42" = Ok 42);
  assert (validate_input_stdlib "bad" = Error "Not an integer: bad");

  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Comparison: Result Monad

Bind Chain

OCaml:

๐Ÿช Show OCaml equivalent
let validate_input s =
parse_int s >>= check_positive >>= check_even

Rust:

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

Rust's ? Operator

Rust:

fn validate_input(s: &str) -> Result<i32, String> {
 let n = parse_int(s)?;       // early return on Err
 let n = check_positive(n)?;  // early return on Err
 check_even(n)                // final result
}

Custom Error Types

OCaml:

๐Ÿช Show OCaml equivalent
type validation_error =
| ParseError of string
| NotPositive of int
| NotEven of int

Rust:

#[derive(Debug)]
enum ValidationError {
 ParseError(String),
 NotPositive(i32),
 NotEven(i32),
}

// ? operator works with From trait for auto-conversion