Question Mark with Option
Use ? in an Option-returning function to return None early when a required optional step is missing.
What it is
The ? operator works with Option as well as Result.
Inside a function returning Option<T>, applying ? to Option<U> unwraps Some(U) or returns None.
This is the optional-data version of early return.
It is ideal for lookup chains where every missing intermediate value means the whole answer is absent.
It often reads better than multiple nested and_then calls.
It also works well with Result::ok()? when a fallible operation should become optional.
However, ? does not convert None into a Result::Err by itself.
In a Result-returning function, convert with ok_or_else(...)?.
The return type chooses the meaning of early exit.
Use that choice deliberately.
How it works
When value? sees Some(inner), evaluation continues with inner.
When it sees None, the enclosing Option function immediately returns None.
The operator relies on Option’s stable Try behavior.
It keeps code linear when each step depends on the previous one.
For Result inside an Option function, call .ok()? to discard the error intentionally.
For Option inside a Result function, call .ok_or_else(...)? to create an error.
That conversion boundary should be visible because it either loses or adds error information.
Use ? when the early-return policy is uniform.
Use match when different missing steps need different behavior.
Use and_then when a compact expression pipeline is clearer than statements.
Example
fn second_word(input: &str) -> Option<&str> {
let mut words = input.split_whitespace();
words.next()?;
Some(words.next()?)
}
fn checked_label(input: &str) -> Option<String> {
let n = input.parse::<u32>().ok()?;
let doubled = n.checked_mul(2)?;
Some(format!("value-{doubled}"))
}
fn main() {
assert_eq!(second_word("hello rust"), Some("rust"));
assert_eq!(second_word("hello"), None);
assert_eq!(checked_label("21"), Some("value-42".to_string()));
assert_eq!(checked_label("not a number"), None);
}Best practice
- ✅ Use
?withOptionwhen every missing step should make the whole function returnNone. - ✅ Use
.ok()?only when discarding aResulterror is intentional. - ✅ Use
.ok_or_else(...)?when optional absence must become aResulterror. - ✅ Prefer statement form with
?when a chain ofand_thencalls becomes hard to read. - ✅ Keep the function return type honest:
Optionfor absence,Resultfor diagnosed failure. - ✅ Add names to intermediate values when they clarify the lookup or parse sequence.
- ✅ Use
let elsewhen the missing branch needs logging or a custom fallback. - ✅ Test the earliest missing step and the latest missing step.
Pitfalls
- ⚠️
?onOptioncannot explain which step was missing. - ⚠️
.ok()?throws away error details; see Swallowing Errors. - ⚠️
?onOptionis not allowed directly in aResult-returning function without conversion. - ⚠️ A function returning
Optionmay be too weak for API boundaries that need diagnostics. - ⚠️ Replacing all
and_thenchains with statements can make simple expressions verbose. - ⚠️ Replacing all statements with chains can make debugging missing values harder.
See also
std: Option & Result Combinators · The Question Mark Operator · Option Combinators · Result Combinators · Converting Between Option and Result · Converting Option to Result with ok_or · Chaining with and_then · Option vs Result · Propagating Errors · let else · Swallowing Errors
Sources
- Rust standard library,
Optionand itsTryimplementation — std, https://doc.rust-lang.org/std/option/enum.Option.html - Rust standard library,
Trytrait documentation — std, https://doc.rust-lang.org/std/ops/trait.Try.html - The Rust Programming Language, recoverable errors and
?— the-book, https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html
