Rust: Early Return on Error Operator (?)
2024-07-08
The ?
Operator: Early Return on Error
The ?
operator is a powerful tool in Rust’s error handling toolkit. It can only be used within functions that return a Result
. It significantly reduces the amount of boilerplate error-handling code you need to write which makes the code more readable and maintainable. It also forces you to think about and handle errors at each step.
It essentially does the following:
Checks for Errors: If the expression on the left of
?
evaluates to anOk
value (a successful result), the value insideOk
is extracted and used as the result of the entire expression.Returns Early on Error: If the expression on the left evaluates to an
Err
value (an error), the?
operator does two things:- It automatically converts the
Err
into the error type expected by the function’s return type. - It immediately returns from the function with that converted error value.
- It automatically converts the
Example 1: Parsing a Number
use std::num::ParseIntError; // Import the error type
fn parse_and_double(s: &str) -> Result<i32, ParseIntError> { // Function returning a Result
let num: i32 = s.parse()?; // Parse string to i32, early return on error
Ok(2 * num) // Double the number if parsing succeeds
}
fn main() {
match parse_and_double("10") {
Ok(n) => println!("Parsed number: {}", n),
Err(e) => println!("Error parsing: {}", e),
}
}
In this example, if s.parse()
fails, the parse_and_double
function immediately returns with the ParseIntError
, and the Ok(2 * num)
line is never executed.
Example 2: Opening a File
use std::fs::File;
use std::io::{Error, Read};
fn read_file_contents(filename: &str) -> Result<String, Error> {
let mut file = File::open(filename)?; // Open file, early return on error
let mut contents = String::new();
file.read_to_string(&mut contents)?; // Read file, early return on error
Ok(contents)
}
Here, if either File::open
or file.read_to_string
fails, the function returns early with the corresponding std::io::Error
.
Example 3: Chaining Operations
fn process_data(input: &str) -> Result<i32, Box<dyn std::error::Error>> {
let num = input.parse::<i32>()?; // Parse, early return on error
let result = some_complex_calculation(num)?; // Do some work, early return on error
Ok(result)
}
In this example, the ?
operator is used multiple times to handle errors gracefully at each step of the data processing pipeline.