The ? Operator
Manual Propagation Is Noisy
Section titled “Manual Propagation Is Noisy”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.
The ? Operator
Section titled “The ? Operator”Placing ? after a Result expression does exactly the same thing as the match above:
- If the value is
Ok(v), it evaluates tov. - If the value is
Err(e), it returnsErr(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)}Chaining ?
Section titled “Chaining ?”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 Returning Result
Section titled “main Returning Result”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.
Error Type Conversion with ?
Section titled “Error Type Conversion with ?”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(())}Compiling…