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

306: ok_or and ok_or_else

Difficulty: 1 Level: Beginner Convert `Option` to `Result` โ€” the bridge between "missing value" and "actionable error."

The Problem This Solves

`Option<T>` says "value or nothing." `Result<T, E>` says "value or error with a reason." These two types live in different worlds, but real code constantly moves between them. A `HashMap` lookup returns `Option`, but your function signature expects `Result`. You need to cross that bridge without losing information. The naive approach โ€” `match opt { Some(v) => Ok(v), None => Err(MyError) }` โ€” is verbose and gets repetitive fast. Every function that wraps an `Option` source into a `Result` return type would need this boilerplate. `ok_or` and `ok_or_else` collapse this into a single method call. They name the reason for absence, turning "maybe nothing" into "error with context" โ€” exactly what callers need to handle failures gracefully.

The Intuition

Think of `ok_or` as adding a label to `None`. When a lookup fails, you don't just get silence โ€” you get `Err("key 'port' not found")`. The `_else` variant is the lazy version: it only builds the error message if it's actually needed, which matters when error construction is expensive (formatting, allocating, syscalls).

How It Works in Rust

// ok_or: eager โ€” error value always evaluated
let port: Result<&str, &str> = config.get("port").ok_or("port not configured");

// ok_or_else: lazy โ€” closure only called when None
let port: Result<&str, String> = config.get("port")
 .ok_or_else(|| format!("key 'port' missing from {:?}", config.keys()));

// Chain with ? for ergonomic propagation
fn get_port(config: &HashMap<&str, &str>) -> Result<u16, String> {
 let s = config.get("port").ok_or_else(|| "port not set".to_string())?;
 s.parse::<u16>().map_err(|e| format!("invalid port: {}", e))
}
The reverse direction โ€” `Result` โ†’ `Option` โ€” uses `.ok()` (keeps the value, discards error) or `.err()` (keeps the error, discards value).

What This Unlocks

Key Differences

ConceptOCamlRust
Option โ†’ Result`Option.to_result ~error:e v``opt.ok_or(e)`
Lazy errorManual `match``opt.ok_or_else(f)`
Result โ†’ Option`Result.to_option``result.ok()`
Error โ†’ OptionN/A (use pattern match)`result.err()`
Common useMonadic bind with `let*`Chaining with `?` operator
//! 306. ok_or and ok_or_else
//!
//! `ok_or(err)` converts `Option<T>` to `Result<T, E>`, providing an error for `None`.

fn lookup<'a>(map: &'a std::collections::HashMap<&str, &str>, key: &str)
    -> Result<&'a str, String>
{
    map.get(key).copied().ok_or_else(|| format!("key '{}' not found", key))
}

fn main() {
    let some42: Option<i32> = Some(42);
    let none: Option<i32> = None;

    // ok_or: eager error
    println!("Some(42).ok_or('missing') = {:?}", some42.ok_or("missing"));
    println!("None.ok_or('missing')     = {:?}", none.ok_or("missing"));

    // ok_or_else: lazy error (only evaluated if None)
    println!("ok_or_else: {:?}", none.ok_or_else(|| format!("error at {}", 42)));

    // Reverse: Result -> Option via .ok() and .err()
    let ok: Result<i32, &str> = Ok(5);
    let err: Result<i32, &str> = Err("bad");
    println!("Ok(5).ok()  = {:?}", ok.ok());
    println!("Err.ok()    = {:?}", err.ok());
    println!("Ok(5).err() = {:?}", ok.err());

    // Practical: HashMap lookup with descriptive errors
    let mut config = std::collections::HashMap::new();
    config.insert("host", "localhost");
    config.insert("port", "8080");

    match lookup(&config, "host") {
        Ok(v) => println!("host = {}", v),
        Err(e) => println!("Error: {}", e),
    }
    match lookup(&config, "db_url") {
        Ok(v) => println!("db_url = {}", v),
        Err(e) => println!("Error: {}", e),
    }

    // Chaining ok_or with ?
    fn get_port(config: &std::collections::HashMap<&str, &str>) -> Result<u16, String> {
        let s = config.get("port").copied().ok_or_else(|| "port not set".to_string())?;
        s.parse::<u16>().map_err(|e| format!("invalid port: {}", e))
    }
    println!("port = {:?}", get_port(&config));
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_ok_or_some() {
        assert_eq!(Some(5i32).ok_or("missing"), Ok(5));
    }

    #[test]
    fn test_ok_or_none() {
        assert_eq!(None::<i32>.ok_or("missing"), Err("missing"));
    }

    #[test]
    fn test_ok_or_else_lazy() {
        let mut called = false;
        let _: Result<i32, &str> = Some(5).ok_or_else(|| { called = true; "err" });
        assert!(!called);
    }

    #[test]
    fn test_result_ok() {
        let r: Result<i32, &str> = Ok(42);
        assert_eq!(r.ok(), Some(42));
    }

    #[test]
    fn test_result_err() {
        let r: Result<i32, &str> = Err("bad");
        assert_eq!(r.err(), Some("bad"));
    }
}
(* 306. ok_or and ok_or_else - OCaml *)

let ok_or err = function
  | Some v -> Ok v
  | None -> Error err

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

let () =
  let some_42 = Some 42 in
  let none : int option = None in

  Printf.printf "Some(42) ok_or: %s\n"
    (match ok_or "not found" some_42 with Ok n -> string_of_int n | Error e -> "Err:" ^ e);
  Printf.printf "None ok_or: %s\n"
    (match ok_or "not found" none with Ok n -> string_of_int n | Error e -> "Err:" ^ e);

  (* Lookup with ok_or *)
  let env = [("HOST", "localhost"); ("PORT", "8080")] in
  let get key = ok_or ("missing key: " ^ key) (List.assoc_opt key env) in
  (match get "HOST" with Ok v -> Printf.printf "HOST=%s\n" v | Error e -> print_endline e);
  (match get "MISSING" with Ok v -> Printf.printf "MISSING=%s\n" v | Error e -> print_endline e)