Skip to content

The ? Operator

Without the ? operator, propagating an error from a called function requires a match expression every time:

fn parse_and_double(s: &str) -> Result<i32, std::num::ParseIntError> {
let n = match s.parse::<i32>() {
Ok(v) => v,
Err(e) => return Err(e),
};
Ok(n * 2)
}

This pattern — unwrap the Ok or return the Err early — is so common that Rust has a dedicated operator for it.

Placing ? after a Result expression does exactly the same thing as the match above:

  • If the value is Ok(v), it evaluates to v.
  • If the value is Err(e), it returns Err(e) immediately from the enclosing function.

The function must return Result (or Option) for ? to work — the compiler enforces this.

use std::num::ParseIntError;
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n: i32 = s.parse()?; // ? returns early on Err
Ok(n * 2)
}

Because ? returns the Ok value directly, you can call multiple fallible functions in sequence without nesting:

use std::num::ParseIntError;
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n: i32 = s.parse()?;
Ok(n * 2)
}
fn chain(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x = parse_and_double(a)?;
let y = parse_and_double(b)?;
Ok(x + y)
}

Each ? either unwraps the success value or short-circuits the whole function. No nested matches, no manual early returns.

main can also return Result<(), Box<dyn std::error::Error>>, which lets you use ? inside main itself:

fn main() -> Result<(), Box<dyn std::error::Error>> {
let n: i32 = "42".parse()?;
println!("parsed: {}", n);
Ok(())
}

Box<dyn std::error::Error> is a trait object that can hold any error type, making it useful in main where you do not want to fix a single concrete error type.

When the error type returned by the called function differs from the error type of the enclosing function, ? automatically calls From::from to convert. This is how a function returning MyError can use ? on a call that returns ParseIntError, as long as From<ParseIntError> for MyError is implemented.

use std::num::ParseIntError;
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
let n: i32 = s.parse()?; // ? returns early on Err
Ok(n * 2)
}
fn chain(a: &str, b: &str) -> Result<i32, ParseIntError> {
let x = parse_and_double(a)?;
let y = parse_and_double(b)?;
Ok(x + y)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let doubled = parse_and_double("21")?;
println!("doubled: {}", doubled);
let sum = chain("10", "5")?;
println!("chain(10, 5): {}", sum);
// Demonstrate early return on error
match parse_and_double("abc") {
Ok(v) => println!("ok: {}", v),
Err(e) => println!("early return caught: {}", e),
}
println!("main returned Ok(())");
Ok(())
}
What does ? do when applied to an Err(e) value?
What constraint must the enclosing function satisfy for ? to compile?
What type is typically used in main() -> Result<(), ...> to accept any error?
How does ? handle mismatched error types between the caller and callee?