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

1007: Result Combinators

Difficulty: Intermediate Category: Error Handling Concept: Functional combinators on `Result`: `and_then`, `or_else`, `map`, `map_err`, `unwrap_or_else` Key Insight: Result combinators let you build error-handling pipelines without any `match` statements โ€” the same functional composition OCaml developers use with `bind` and `map`.
// 1007: Result Combinators
// and_then, or_else, map, map_err, unwrap_or_else

fn parse_int(s: &str) -> Result<i64, String> {
    s.parse::<i64>().map_err(|e| format!("not an int: {} ({})", s, e))
}

fn double_if_positive(n: i64) -> Result<i64, String> {
    if n > 0 {
        Ok(n * 2)
    } else {
        Err("must be positive".into())
    }
}

// Approach 1: Chaining with and_then (flatmap/bind)
fn process_chain(s: &str) -> Result<String, String> {
    parse_int(s)
        .and_then(double_if_positive)
        .map(|n| n.to_string())
}

// Approach 2: Using map, map_err, or_else, unwrap_or_else
fn process_with_fallback(s: &str) -> String {
    parse_int(s)
        .and_then(double_if_positive)
        .map(|n| n.to_string())
        .map_err(|e| format!("FALLBACK: {}", e))
        .unwrap_or_else(|e| e)
}

fn process_or_else(s: &str) -> Result<i64, String> {
    parse_int(s)
        .and_then(double_if_positive)
        .or_else(|_| Ok(0)) // fallback to 0 on any error
}

fn main() {
    println!("chain '5': {:?}", process_chain("5"));
    println!("chain '-3': {:?}", process_chain("-3"));
    println!("fallback 'abc': {}", process_with_fallback("abc"));
    println!("or_else '-1': {:?}", process_or_else("-1"));
    println!("Run `cargo test` to verify all examples.");
}

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

    #[test]
    fn test_and_then_success() {
        assert_eq!(process_chain("5"), Ok("10".to_string()));
    }

    #[test]
    fn test_and_then_negative() {
        assert_eq!(process_chain("-3"), Err("must be positive".to_string()));
    }

    #[test]
    fn test_and_then_parse_fail() {
        assert!(process_chain("abc").is_err());
    }

    #[test]
    fn test_map() {
        let result: Result<i64, String> = Ok(5);
        assert_eq!(result.map(|n| n * 2), Ok(10));
    }

    #[test]
    fn test_map_err() {
        let result: Result<i64, &str> = Err("low");
        assert_eq!(result.map_err(|e| e.to_uppercase()), Err("LOW".to_string()));
    }

    #[test]
    fn test_or_else() {
        assert_eq!(process_or_else("-1"), Ok(0));
        assert_eq!(process_or_else("5"), Ok(10));
    }

    #[test]
    fn test_unwrap_or_else() {
        let result: Result<i64, String> = Err("fail".into());
        assert_eq!(result.unwrap_or_else(|_| 99), 99);

        let result: Result<i64, String> = Ok(42);
        assert_eq!(result.unwrap_or_else(|_| 99), 42);
    }

    #[test]
    fn test_fallback_string() {
        assert_eq!(process_with_fallback("5"), "10");
        assert!(process_with_fallback("abc").starts_with("FALLBACK"));
    }
}
(* 1007: Result Combinators *)
(* Functional transforms on Result values *)

(* OCaml Result module combinators *)
let map f = function Ok v -> Ok (f v) | Error e -> Error e
let map_error f = function Ok v -> Ok v | Error e -> Error (f e)
let bind f = function Ok v -> f v | Error e -> Error e  (* and_then *)

(* Approach 1: Manual pattern matching *)
let parse_int s =
  match int_of_string_opt s with
  | Some n -> Ok n
  | None -> Error (Printf.sprintf "not an int: %s" s)

let double_if_positive n =
  if n > 0 then Ok (n * 2)
  else Error "must be positive"

let process_manual s =
  match parse_int s with
  | Error e -> Error e
  | Ok n ->
    match double_if_positive n with
    | Error e -> Error e
    | Ok v -> Ok (string_of_int v)

(* Approach 2: Using combinators *)
let process_combinators s =
  parse_int s
  |> bind double_if_positive
  |> map string_of_int

(* or_else equivalent *)
let or_else f = function
  | Ok v -> Ok v
  | Error e -> f e

let with_default default = function
  | Ok v -> v
  | Error _ -> default

let test_manual () =
  assert (process_manual "5" = Ok "10");
  assert (process_manual "-3" = Error "must be positive");
  assert (process_manual "abc" = Error "not an int: abc");
  Printf.printf "  Approach 1 (manual matching): passed\n"

let test_combinators () =
  assert (process_combinators "5" = Ok "10");
  assert (process_combinators "-3" = Error "must be positive");
  (* map_error *)
  let r = map_error String.uppercase_ascii (Error "low") in
  assert (r = Error "LOW");
  (* or_else *)
  let r = or_else (fun _ -> Ok 0) (Error "fail") in
  assert (r = Ok 0);
  (* unwrap_or / with_default *)
  assert (with_default 99 (Error "fail") = 99);
  assert (with_default 99 (Ok 42) = 42);
  Printf.printf "  Approach 2 (combinators): passed\n"

let () =
  Printf.printf "Testing result combinators:\n";
  test_manual ();
  test_combinators ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Result Combinators โ€” Comparison

Core Insight

Both OCaml and Rust treat Result as a monad with `map` (functor) and `bind`/`and_then` (monadic bind). Rust adds more built-in combinators.

OCaml Approach

  • `Result.map`, `Result.bind` in stdlib (OCaml 4.08+)
  • Custom `map_error`, `or_else` typically hand-written
  • Pipeline via `|>` operator
  • `Option.value ~default` for unwrap-with-default

Rust Approach

  • Rich built-in: `map`, `map_err`, `and_then`, `or_else`, `unwrap_or_else`, `unwrap_or_default`
  • Method chaining with `.` notation
  • `?` operator as syntactic sugar for `and_then` + early return
  • `ok()`, `err()` to convert between Result and Option

Comparison Table

CombinatorOCamlRust
map`Result.map f r``r.map(f)`
flatmap/bind`Result.bind r f``r.and_then(f)`
map errorcustom `map_error``r.map_err(f)`
fallbackcustom `or_else``r.or_else(f)`
default`Result.value r ~default``r.unwrap_or(v)`
lazy defaultcustom`r.unwrap_or_else(f)`