Under The Hood
backend rust Rust 1.x

Result<T, E> & Option<T>: The ? Operator Lowering and From Conversions

Last updated
Prerequisites:
Rust enums, generics, and traits
Understanding of error propagation patterns
  • rust
  • error-handling
  • result
  • option
  • question-mark
  • from

Read at your depth

The practical view

Option<T> (Some/None) and Result<T, E> (Ok/Err) are the standard way to handle optional and fallible operations in Rust. The ? operator propagates errors: fn read() -> Result<String, io::Error> { let s = fs::read_to_string(path)?; Ok(s) } — if the inner call returns Err, ? returns it from the function immediately. Option also supports ?: in a function returning Option<T>, None propagates. The From trait enables automatic error conversion: if your function returns Result<_, MyError> and a ? encounters an io::Error, the compiler inserts From::from(io::Error) -> MyError (if implemented). Use map, and_then, unwrap_or, match for handling without early returns.

The same idea in other frameworks

react equivalent
N/A — Rust language feature.

Legacy vs modern

Nested match error handling vs ? operator

Manually matching every error nests the code and obscures the happy path; ? flattens it into a linear flow with automatic conversion.

before → after
Nested matches (error pyramid)
match fs::read_to_string(path) {
  Ok(s) => match s.parse::<i32>() {
    Ok(n) => Ok(n),
    Err(e) => Err(e.into()), // ❌ OLD: manual conversion per level
  },
  Err(e) => Err(e.into()),
}
? operator with From conversion
fn read_number(path: &str) -> Result<i32, Box<dyn std::error::Error>> {
  let s = fs::read_to_string(path)?; // ✅ NEW: io::Error -> Box<dyn Error> via From
  Ok(s.trim().parse::<i32>()?) // ✅ NEW: ParseIntError -> Box<dyn Error> via From
}

Interview gotchas

Context

This tests the desugaring — interviewers want the exact match + early-return + From::from translation, which explains the type requirements of ?.

The mechanical answer

The ? operator desugars to a match with an early return: for Result, match expr { Ok(v) => v, Err(e) => return Err(From::from(e)) }; for Option, match expr { Some(v) => v, None => return None }. The From::from(e) is the crucial part: the enclosing function's error type must implement From<the inner error type>, and the compiler inserts the conversion automatically. This is why ? composes across different error sources: each inner error type converts into your unified error type via its From impl. If no From impl exists, the code doesn't compile (a compile-time guarantee that error propagation is type-correct). The Try trait generalizes ? beyond Result/Option (for custom types implementing Try).

Trap

The naive answer is '? just unwraps and returns the error'. The From conversion is mandatory and automatic — if the error types don't convert, it's a compile error, not a runtime surprise. Another trap: thinking ? can be used in any function — it requires the function to return Result/Option (or Try); a function returning a plain value cannot use ?. Candidates who explain the From requirement show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.