Rust Error Propagation
The ? operator propagates an error out of a function automatically. On Err, ? returns that error from the function at once.
The long way: match and return the error
Without ?, each fallible call needs a match that returns the error by hand:
fn first_number(text: &str) -> Result<i32, std::num::ParseIntError> {
let n: i32 = match text.parse() {
Ok(n) => n,
Err(e) => return Err(e),
};
Ok(n)
}
println!("{:?}", first_number("42"));Give me the value, or stop here
Write ? after a call that returns a Result. On Ok, ? hands you the value inside, and the function carries on.
fn first_number(text: &str) -> Result<i32, std::num::ParseIntError> {
let n: i32 = text.parse()?;
Ok(n)
}On Err, ? returns at once
The same function, fed good text and bad text. The bad call never reaches an Ok — ? returns the error straight away.
println!("{:?}", first_number("42"));
println!("{:?}", first_number("abc"));? works on Option too
On None, ? returns None from the function at once — the same shortcut, one type over.
fn first_word(text: &str) -> Option<&str> {
let word = text.split_whitespace().next()?;
Some(word)
}
println!("{:?}", first_word("hello rust"));
println!("{:?}", first_word(""));Calls can chain after ?
Each ? is a checkpoint on the same line. The line stops at the first failure.
use std::fs::File;
use std::io::Read;
let mut text = String::new();
File::open(path)?.read_to_string(&mut text)?;Change an error in one place
map_err rewrites an error just here, without a whole From impl. The closure runs only when the Result is an Err.
let n = "abc".parse::<i32>()
.map_err(|_| String::from("not a number"));
println!("{:?}", n);Swap the layers with transpose
When an Option holds a Result, transpose swaps the layers, so the Result sits outside where ? can work on it.
let wrapped: Option<Result<i32, &str>> = Some(Ok(5));
println!("{:?}", wrapped.transpose());Example
use std::fs;
fn first_line(path: &str) -> Result<String, std::io::Error> {
let text = fs::read_to_string(path)?;
Ok(text.lines().next().unwrap_or("").to_string())
}
fn main() {
println!("{:?}", first_line("/nonexistent.txt").map(|_| ()).unwrap_err().kind());
}Key points
- The error is converted automatically if a From conversion exists for it
- main itself may return a Result, which is handy for small programs
- ? only works where the return type fits: Result ? in a function returning Result, Option ? in a function returning Option — anywhere else is error E0277
- Mixing the two needs a conversion step: .ok() turns a Result into an Option, and .ok_or(error) turns an Option into a Result
- In very old code you may see the try! macro doing the same job as ? — ? replaced it, and new code uses ?
In this example, first_line reads a whole file into text. The ? after read_to_string does two jobs. If reading fails, first_line stops and returns that error to its caller. If reading works, text holds the file's contents and the function continues, returning the first line as Ok. main calls it with a path that does not exist. So the error travels out. And the program prints its kind: NotFound. One small ? replaced a whole match.
Note
? is only sugar for a plain match: Ok(v) continues with v, Err(e) returns the error.
Nothing is hidden or swallowed.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
What does ? do when the Result is an Err?