πŸ¦€ 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

046: Result Map

Difficulty: 1 Level: Foundations Transform `Result` values without unwrapping them β€” keep errors flowing through cleanly.

The Problem This Solves

You've called a function that returned `Result<i64, AppError>`. Now you want to double that number. The naΓ―ve approach: unwrap the result, double it, wrap it back. But that forces you to write error-handling boilerplate at every transformation step, even when you're just doing math on the success value. This is the "pyramid of doom" problem familiar from JavaScript callbacks and Python nested `try/except` blocks. Each transformation adds another level of nesting. With five transformations, you've got deeply nested code that's hard to read and easy to get wrong. `Result::map` solves this elegantly. It applies a function to the `Ok` value, and passes `Err` through untouched. You transform the happy path; errors take care of themselves. The result is flat, readable code where errors are handled once at the end, not at every step.

The Intuition

You already know `Array.map()` in JavaScript or Python's list comprehension: apply a function to each element. `Result::map` is the same idea, but for a "container" that holds either a success value or an error. The rule is simple: `map_err` is the mirror image: transform the error if present, pass `Ok` through. `and_then` (called `flatMap` in other languages) is where it gets powerful: instead of a plain function `T β†’ U`, you give it a function `T β†’ Result<U, E>`. This lets you chain operations that can themselves fail β€” like Promise.then() with async functions, but synchronous and fully explicit.

How It Works in Rust

fn parse_int(s: &str) -> Result<i64, AppError> { ... }
fn check_range(x: i64, min: i64, max: i64) -> Result<i64, AppError> { ... }

// map: transform Ok value (plain function, can't fail)
let doubled = parse_int("5").map(|x| x * 2);
// Ok(10) β€” the closure never produces an Err

// map_err: transform the error type (e.g. to log-friendly String)
let stringified = parse_int("bad").map_err(|e| format!("{:?}", e));
// Err("ParseError(...)")

// and_then: chain a fallible operation
// Use this when the next step can ALSO return Err
let result = parse_int("100")
 .map(|x| x / 4)                          // infallible: just math
 .and_then(|x| check_range(x, 0, 50));     // fallible: might be out of range
// Ok(25) β€” 100/4=25, 25 is in [0,50]

// Errors short-circuit: the chain stops at the first Err
let result2 = parse_int("1000")
 .map(|x| x / 4)                          // Ok(250)
 .and_then(|x| check_range(x, 0, 50));     // Err(OutOfRange(250))
// Err β€” check_range fails, map step was wasted but harmless

// Collect a Vec of Results into Result<Vec<...>>
// None if ANY element is Err β€” the whole thing fails
let parsed: Result<Vec<i64>, AppError> =
 vec!["1", "2", "3"].iter().map(|s| parse_int(s)).collect();
// Ok([1, 2, 3])

What This Unlocks

Key Differences

ConceptOCamlRust
Transform Ok value`Result.map f r``r.map(f)`
Transform Err value`Result.map_error f r``r.map_err(f)`
Chain fallible op`Result.bind r f` or `r >>= f``r.and_then(f)`
Recover from error`Result.catch` / custom`r.or_else(\e\...)`
All-or-nothing collect`List.map f xs \> Result.all``xs.iter().map(f).collect::<Result<Vec<_>,_>>()`
Chaining style`>>=` operator (infix)Method chain (`.and_then(...)`)
// Result Map β€” 99 Problems #46
// Transform Result values with map, map_err, and_then, or_else.

#[derive(Debug, PartialEq, Clone)]
enum AppError {
    ParseError(String),
    DivisionByZero,
    OutOfRange(i64),
}

fn parse_int(s: &str) -> Result<i64, AppError> {
    s.trim().parse::<i64>()
        .map_err(|_| AppError::ParseError(format!("'{}' is not an integer", s)))
}

fn safe_div(a: i64, b: i64) -> Result<i64, AppError> {
    if b == 0 { Err(AppError::DivisionByZero) } else { Ok(a / b) }
}

fn check_range(x: i64, min: i64, max: i64) -> Result<i64, AppError> {
    if x >= min && x <= max { Ok(x) } else { Err(AppError::OutOfRange(x)) }
}

/// Double a parsed integer.
fn parse_and_double(s: &str) -> Result<i64, AppError> {
    parse_int(s).map(|x| x * 2)
}

/// Transform error type.
fn parse_as_string_error(s: &str) -> Result<i64, String> {
    parse_int(s).map_err(|e| format!("{:?}", e))
}

fn main() {
    // map transforms Ok, passes Err through
    println!("parse('5').map(*2)  = {:?}", parse_and_double("5"));
    println!("parse('x').map(*2)  = {:?}", parse_and_double("x"));

    // map_err transforms Err, passes Ok through
    println!("map_err:            = {:?}", parse_as_string_error("bad"));

    // Chaining maps
    let r = parse_int("100")
        .map(|x| x / 4)
        .and_then(|x| check_range(x, 0, 50));
    println!("100 / 4 in [0,50]   = {:?}", r);

    let r2 = parse_int("1000")
        .map(|x| x / 4)
        .and_then(|x| check_range(x, 0, 50));
    println!("1000 / 4 in [0,50]  = {:?}", r2);

    // or_else: recover from errors
    let recovered = safe_div(10, 0).or_else(|_| Ok::<i64, AppError>(-1));
    println!("div 0 recovered:    = {:?}", recovered);

    // collect Results from iterator
    let strings = vec!["1", "2", "3"];
    let parsed: Result<Vec<i64>, AppError> = strings.iter().map(|s| parse_int(s)).collect();
    println!("collect all ok:     = {:?}", parsed);

    let mixed = vec!["1", "x", "3"];
    let mixed_result: Result<Vec<i64>, AppError> = mixed.iter().map(|s| parse_int(s)).collect();
    println!("collect with error: = {:?}", mixed_result);
}

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

    #[test]
    fn test_map_ok() {
        assert_eq!(parse_and_double("5"), Ok(10));
    }

    #[test]
    fn test_map_err_pass_through() {
        assert!(parse_and_double("x").is_err());
    }

    #[test]
    fn test_map_err_transform() {
        assert!(parse_as_string_error("bad").is_err());
        let e = parse_as_string_error("bad").unwrap_err();
        assert!(e.contains("ParseError"));
    }

    #[test]
    fn test_collect_ok() {
        let strings = vec!["1", "2", "3"];
        let result: Result<Vec<i64>, AppError> = strings.iter().map(|s| parse_int(s)).collect();
        assert_eq!(result, Ok(vec![1, 2, 3]));
    }

    #[test]
    fn test_or_else_recovery() {
        let r = safe_div(10, 0).or_else(|_| Ok::<i64, AppError>(0));
        assert_eq!(r, Ok(0));
    }
}
(* Result Map *)
(* OCaml 99 Problems #46 *)

(* Implementation for example 46 *)

(* Tests *)
let () =
  (* Add tests *)
  print_endline "βœ“ OCaml tests passed"