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

1008: Option to Result Conversion

Difficulty: Beginner Category: Error Handling Concept: Converting `Option<T>` to `Result<T, E>` using `ok_or` and `ok_or_else` Key Insight: `ok_or_else` is lazy (only constructs the error if needed), making it preferred when the error value involves allocation โ€” the same eager vs lazy distinction as OCaml's `fun () -> ...` thunks.
// 1008: Option to Result Conversion
// Convert Option<T> to Result<T, E> with ok_or / ok_or_else

use std::collections::HashMap;

fn build_users() -> HashMap<String, (String, u32)> {
    let mut m = HashMap::new();
    m.insert("Alice".into(), ("alice@ex.com".into(), 30));
    m.insert("Bob".into(), ("bob@ex.com".into(), 17));
    m
}

// Approach 1: ok_or โ€” eager error value
fn find_user_eager<'a>(users: &'a HashMap<String, (String, u32)>, name: &str) -> Result<&'a (String, u32), String> {
    users.get(name).ok_or(format!("user not found: {}", name))
}

// Approach 2: ok_or_else โ€” lazy error (avoids allocation if Some)
fn find_user_lazy<'a>(users: &'a HashMap<String, (String, u32)>, name: &str) -> Result<&'a (String, u32), String> {
    users.get(name).ok_or_else(|| format!("user not found: {}", name))
}

// Approach 3: Chaining Option->Result in a pipeline
fn find_and_validate(
    users: &HashMap<String, (String, u32)>,
    name: &str,
    min_age: u32,
) -> Result<(String, u32), String> {
    users
        .get(name)
        .ok_or_else(|| format!("user not found: {}", name))
        .and_then(|(email, age)| {
            if *age >= min_age {
                Ok((email.clone(), *age))
            } else {
                Err(format!("{} is too young ({} < {})", name, age, min_age))
            }
        })
}


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

    #[test]
    fn test_ok_or_found() {
        let users = build_users();
        let result = find_user_eager(&users, "Alice");
        assert!(result.is_ok());
        assert_eq!(result.unwrap().1, 30);
    }

    #[test]
    fn test_ok_or_not_found() {
        let users = build_users();
        let result = find_user_eager(&users, "Unknown");
        assert_eq!(result.unwrap_err(), "user not found: Unknown");
    }

    #[test]
    fn test_ok_or_else_lazy() {
        let users = build_users();
        assert!(find_user_lazy(&users, "Bob").is_ok());
        assert!(find_user_lazy(&users, "Nobody").is_err());
    }

    #[test]
    fn test_validate_success() {
        let users = build_users();
        let result = find_and_validate(&users, "Alice", 18);
        assert_eq!(result.unwrap(), ("alice@ex.com".into(), 30));
    }

    #[test]
    fn test_validate_too_young() {
        let users = build_users();
        let result = find_and_validate(&users, "Bob", 18);
        assert!(result.unwrap_err().contains("too young"));
    }

    #[test]
    fn test_validate_not_found() {
        let users = build_users();
        let result = find_and_validate(&users, "Nobody", 18);
        assert!(result.unwrap_err().contains("not found"));
    }

    #[test]
    fn test_option_methods() {
        // Direct Option -> Result conversions
        assert_eq!(Some(42).ok_or("missing"), Ok(42));
        assert_eq!(None::<i32>.ok_or("missing"), Err("missing"));

        // Result -> Option conversions
        assert_eq!(Ok::<i32, &str>(42).ok(), Some(42));
        assert_eq!(Err::<i32, &str>("fail").ok(), None);
    }
}
(* 1008: Option to Result Conversion *)
(* Converting Option to Result with error context *)

(* Approach 1: Pattern matching *)
let find_user_manual users name =
  match List.assoc_opt name users with
  | Some user -> Ok user
  | None -> Error (Printf.sprintf "user not found: %s" name)

(* Approach 2: Helper function (like ok_or) *)
let ok_or error = function
  | Some v -> Ok v
  | None -> Error error

let ok_or_else error_fn = function
  | Some v -> Ok v
  | None -> Error (error_fn ())

let find_user users name =
  List.assoc_opt name users
  |> ok_or_else (fun () -> Printf.sprintf "user not found: %s" name)

(* Approach 3: Chaining Option-to-Result in a pipeline *)
let find_and_validate users name min_age =
  List.assoc_opt name users
  |> ok_or_else (fun () -> Printf.sprintf "user not found: %s" name)
  |> Result.bind (fun (_, age) ->
       if age >= min_age then Ok (name, age)
       else Error (Printf.sprintf "%s is too young (%d < %d)" name age min_age))

let users = [("Alice", ("alice@ex.com", 30)); ("Bob", ("bob@ex.com", 17))]

let test_manual () =
  assert (find_user_manual users "Alice" = Ok ("alice@ex.com", 30));
  assert (find_user_manual users "Unknown" = Error "user not found: Unknown");
  Printf.printf "  Approach 1 (manual): passed\n"

let test_ok_or () =
  assert (ok_or "missing" (Some 42) = Ok 42);
  assert (ok_or "missing" None = Error "missing");
  assert (find_user users "Alice" = Ok ("alice@ex.com", 30));
  Printf.printf "  Approach 2 (ok_or helper): passed\n"

let test_chain () =
  assert (find_and_validate users "Alice" 18 = Ok ("Alice", 30));
  (match find_and_validate users "Bob" 18 with
   | Error msg -> assert (String.length msg > 0)
   | Ok _ -> assert false);
  (match find_and_validate users "Unknown" 18 with
   | Error msg -> assert (msg = "user not found: Unknown")
   | Ok _ -> assert false);
  Printf.printf "  Approach 3 (chained pipeline): passed\n"

let () =
  Printf.printf "Testing option to result:\n";
  test_manual ();
  test_ok_or ();
  test_chain ();
  Printf.printf "โœ“ All tests passed\n"

๐Ÿ“Š Detailed Comparison

Option to Result โ€” Comparison

Core Insight

Both languages need to bridge the gap between "absence" (Option/None) and "error" (Result/Error). The conversion adds semantic meaning to what was just a missing value.

OCaml Approach

  • Pattern match on `option` and return `Ok`/`Error`
  • Custom `ok_or` and `ok_or_else` helpers (not in stdlib before 4.08)
  • Lazy version uses `fun () -> error_value` thunk

Rust Approach

  • Built-in `Option::ok_or(err)` and `Option::ok_or_else(|| err)`
  • Reverse: `Result::ok()` and `Result::err()` to get Options
  • Chains naturally: `.ok_or_else(|| ...)?.and_then(...)`

Comparison Table

AspectOCamlRust
Option to ResultCustom helper or match`.ok_or()` / `.ok_or_else()`
Result to OptionCustom helper`.ok()` / `.err()`
Lazy error`fun () -> ...` thunk`\\...` closure
Chaining`\>` pipeline`.method()` chain
In stdlibSince 4.08 (partial)Always available