Option<T> and Result<T, E> in Depth

ok_or() and ok_or_else()

The .ok_or(err) and .ok_or_else(err_fn) methods convert an Option<T> into a Result<T, E>, mapping Some(v) to Ok(v) and mapping None to Err(err) so you can propagate missing values with the ? operator inside functions that return Result.

Signatures and Evaluation Strategy

impl<T> Option<T> {
    pub fn ok_or<E>(self, err: E) -> Result<T, E>;
    pub fn ok_or_else<E, F: FnOnce() -> E>(self, err: F) -> Result<T, E>;
}
Method Evaluation of Error Value Best Used When...
opt.ok_or(err) Eager (err is constructed before calling ok_or, even if opt is Some) err is a cheap constant, unit enum variant (MyError::NotFound), or &'static str
opt.ok_or_else(|| ...) Lazy (closure runs only when opt is None) Constructing the error allocates (format!(...), String::from(...), Box::new(...)) or calls a function
res.ok() (Inverse on Result<T, E>) Discards Err(e) Converts Result<T, E> -> Option<T> (Ok(v) -> Some(v), Err(_) -> None)
Option<T>                              Result<T, E>
  ├── Some(v)  ── .ok_or_else(f) ──>     Ok(v)       (f is NOT called)
  └── None     ── .ok_or_else(f) ──>     Err(f())    (f is called lazily)

Bridging Option Lookups into Result Functions with ?

When a function returns Result<T, AppError> and calls a method that returns Option<U> (such as slice.first(), map.get(), or str::split_once()), chain .ok_or(...) or .ok_or_else(...) before ?:

use std::collections::HashMap;

#[derive(Debug)]
enum ConfigError {
    MissingHeader,
    MissingKey(String),
}

fn extract_host(raw_line: &str, env: &HashMap<&str, &str>) -> Result<String, ConfigError> {
    // 1. Cheap unit enum variant -> use eager `.ok_or()`:
    let (_prefix, key) = raw_line
        .split_once('=')
        .ok_or(ConfigError::MissingHeader)?;

    // 2. Error allocates a `String` -> use lazy `.ok_or_else()`:
    let host = env
        .get(key)
        .ok_or_else(|| ConfigError::MissingKey(key.to_owned()))?;

    Ok((*host).to_owned())
}

fn main() {
    let env = HashMap::from([("DB_HOST", "10.0.0.5")]);

    println!("Valid:   {:?}", extract_host("target=DB_HOST", &env));
    println!("Bad fmt: {:?}", extract_host("invalid_line", &env));
    println!("Missing: {:?}", extract_host("target=REDIS_HOST", &env));
}
Valid:   Ok("10.0.0.5")
Bad fmt: Err(MissingHeader)
Missing: Err(MissingKey("REDIS_HOST"))

(Note: Clippy's or_fun_call lint warns automatically if you pass a function call or format!(...) expression to eager .ok_or(...) instead of .ok_or_else(|| ...).)