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

with Option

Applying the ? operator to an Option<T> inside a function or closure that returns Option<U> unwraps Some(v) into v, or immediately returns None from the enclosing function when the value is None.

Desugaring of opt?

let val = expr?;

// Desugars to:
let val = match expr {
    Some(v) => v,
    None => return None,
};

Flattening Deep Optional Navigation Chains with ?

Without ?, traversing a sequence of optional lookups (HashMap::get, slice::first, str::strip_prefix, char::to_digit) requires either deeply nested if let Some(...) blocks or a long .and_then(...) chain. Using ? lets you write linear, top-to-bottom code:

struct Server {
    tls_cert_path: Option<String>,
}

struct Cluster {
    primary: Option<Server>,
}

fn primary_cert_extension(cluster: &Cluster) -> Option<&str> {
    let server = cluster.primary.as_ref()?;
    let path = server.tls_cert_path.as_deref()?;
    let (_name, ext) = path.rsplit_once('.')?;
    Some(ext)
}

fn main() {
    let valid_cluster = Cluster {
        primary: Some(Server {
            tls_cert_path: Some(String::from("/etc/ssl/server.pem")),
        }),
    };
    let no_tls_cluster = Cluster {
        primary: Some(Server {
            tls_cert_path: None,
        }),
    };

    println!("Valid cluster ext:  {:?}", primary_cert_extension(&valid_cluster));
    println!("No-TLS cluster ext: {:?}", primary_cert_extension(&no_tls_cluster));
}
Valid cluster ext:  Some("pem")
No-TLS cluster ext: None

Cannot Mix Option? Directly in a Result Function (E0277)

The ? operator does not implicitly convert Option<T> into Result<T, E> (or vice versa), because None carries no error information telling the compiler which E value to construct:

// ❌ does not compile
fn first_byte(s: &str) -> Result<u8, &'static str> {
    let b = s.bytes().next()?; // Error: `next()` returns `Option<u8>`, not `Result`
    Ok(b)
}

fn main() {}
error[E0277]: the `?` operator can only be used on `Option`s, not `Result`s, in a function that returns `Option`
 --> src/main.rs:3:29
  |
2 | fn first_byte(s: &str) -> Result<u8, &'static str> {
  | -------------------------------------------------- this function returns a `Result`
3 |     let b = s.bytes().next()?;
  |                             ^ use `.ok_or(...)?` to provide an error compatible with `Result<u8, &str>`

To bridge between Option and Result when using ?:

  • Inside a function returning Result<T, E>, convert an Option<U> with .ok_or(err)? or .ok_or_else(|| ...)?.
  • Inside a function returning Option<T>, convert a Result<U, E> with .ok()?.