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

Difficulty: Intermediate Category: Error Handling Concept: Result chaining with `and_then` (bind) and the `?` operator Key Insight: Result forms a monad โ€” `and_then` is bind, `?` is syntactic sugar for early return on `Err`.
// 056: Result as Monad
// Chain fallible operations with and_then and ?

use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
enum CalcError {
    Parse(String),
    DivByZero,
}

fn parse_int(s: &str) -> Result<i32, CalcError> {
    s.parse::<i32>().map_err(|e| CalcError::Parse(e.to_string()))
}

fn safe_div(a: i32, b: i32) -> Result<i32, CalcError> {
    if b == 0 { Err(CalcError::DivByZero) } else { Ok(a / b) }
}

// Approach 1: Using and_then (monadic bind)
fn compute_bind(s1: &str, s2: &str) -> Result<i32, CalcError> {
    parse_int(s1).and_then(|a| {
        parse_int(s2).and_then(|b| safe_div(a, b))
    })
}

// Approach 2: Using ? operator (syntactic sugar for bind)
fn compute_question(s1: &str, s2: &str) -> Result<i32, CalcError> {
    let a = parse_int(s1)?;
    let b = parse_int(s2)?;
    safe_div(a, b)
}

// Approach 3: Chained pipeline
fn pipeline(s: &str) -> Result<i32, CalcError> {
    parse_int(s)
        .and_then(|n| safe_div(n, 2))
        .map(|n| n + 1)
        .map(|n| n * 2)
}

fn main() {
    println!("compute_bind(10, 3) = {:?}", compute_bind("10", "3"));
    println!("compute_question(10, 0) = {:?}", compute_question("10", "0"));
    println!("pipeline(10) = {:?}", pipeline("10"));
    println!("pipeline(abc) = {:?}", pipeline("abc"));
}

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

    #[test]
    fn test_parse_int() {
        assert_eq!(parse_int("42"), Ok(42));
        assert!(parse_int("abc").is_err());
    }

    #[test]
    fn test_compute_bind() {
        assert_eq!(compute_bind("10", "3"), Ok(3));
        assert_eq!(compute_bind("10", "0"), Err(CalcError::DivByZero));
        assert!(compute_bind("abc", "3").is_err());
    }

    #[test]
    fn test_compute_question() {
        assert_eq!(compute_question("10", "3"), Ok(3));
        assert_eq!(compute_question("10", "0"), Err(CalcError::DivByZero));
    }

    #[test]
    fn test_pipeline() {
        assert_eq!(pipeline("10"), Ok(12));
        assert!(pipeline("abc").is_err());
    }
}
(* 056: Result as Monad *)
(* Chain fallible operations with bind *)

(* Approach 1: Explicit pattern matching *)
let parse_int s =
  match int_of_string_opt s with
  | Some n -> Ok n
  | None -> Error (Printf.sprintf "Not a number: %s" s)

let safe_div a b =
  if b = 0 then Error "Division by zero"
  else Ok (a / b)

let compute_explicit s1 s2 =
  match parse_int s1 with
  | Error e -> Error e
  | Ok a ->
    match parse_int s2 with
    | Error e -> Error e
    | Ok b -> safe_div a b

(* Approach 2: Using Result.bind *)
let compute_bind s1 s2 =
  parse_int s1
  |> Result.bind (fun a ->
    parse_int s2
    |> Result.bind (fun b ->
      safe_div a b))

(* Approach 3: Pipeline with map and bind *)
let add_one r = Result.map (fun x -> x + 1) r
let double r = Result.map (fun x -> x * 2) r

let pipeline s =
  parse_int s
  |> Result.bind (fun n -> safe_div n 2)
  |> Result.map (fun n -> n + 1)
  |> Result.map (fun n -> n * 2)

(* Tests *)
let () =
  assert (parse_int "42" = Ok 42);
  assert (parse_int "abc" = Error "Not a number: abc");
  assert (compute_explicit "10" "3" = Ok 3);
  assert (compute_explicit "10" "0" = Error "Division by zero");
  assert (compute_explicit "abc" "3" = Error "Not a number: abc");
  assert (compute_bind "10" "3" = Ok 3);
  assert (compute_bind "10" "0" = Error "Division by zero");
  assert (pipeline "10" = Ok 12);
  assert (pipeline "abc" = Error "Not a number: abc");
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Core Insight

A monad is a type with `return` (wrap a value) and `bind` (chain operations that may fail). Result is a monad: `Ok` is return, `and_then`/`bind` chains fallible operations, short-circuiting on `Err`.

OCaml Approach

  • `Result.bind result f` โ€” chains fallible functions
  • `Result.map result f` โ€” transforms the Ok value
  • Manual pattern matching as alternative
  • `let*` syntax with binding operators (OCaml 4.08+)

Rust Approach

  • `.and_then(f)` โ€” monadic bind
  • `.map(f)` โ€” functor map
  • `?` operator โ€” desugar to match + early return
  • Method chaining is idiomatic

Comparison Table

OperationOCamlRust
Return/wrap`Ok x``Ok(x)`
Bind`Result.bind r f``r.and_then(f)`
Map`Result.map f r``r.map(f)`
Sugar`let* x = r in ...``let x = r?;`
Short-circuitPattern match`?` operator