Skip to main content
  1. 📝 Notes/

#learn-rust What is the question mark (?) operator?

The question mark operator provides an alternative and concise approach to handle errors in functions that return the Result or Option type. By using the ? operator, we can reduce the amount of code needed to handle returning Err or None from Result<T, Err> or Option<T> types.

Here’s a quick example:

Instead of using a match statement to check the success of a function do_something() that returns a Result<T, Err> type, we can achieve the same result with the following shorter syntax:

let a = match do_something() {
  Ok(a) => a,
  Err(e) => return Err(e),
}

While this is quite expressive, it can be shortened to:

let a = do_something()?;

This operator unwraps valid values and propagates erroneous values to the calling function.

Now, the question arises: should we always use ?? Well, as is often the case, it depends.

The ? operator simplifies code and enhances readability by reducing boilerplate. However, it can only be used in functions that return Result or Option types. Therefore, changing the return type of a function is necessary to utilize this operator, which may not always be feasible or desirable. Furthermore, the ? operator is not available in main() functions.

Additionally, while the ? operator is thorough and concise, it lacks the ability to provide custom behavior. There might be situations where you prefer to:

  • Invoke custom behavior, such as error recovery
  • Create a custom error
  • Even panic

In such cases, the match statement is still required to fulfill these specific needs.

Let’s consider another example that demonstrates error propagation across multiple function calls:

use std::io;

fn get_user_age() -> Result<u8, io::Error> {
    // Simulating an error condition
    Err(io::Error::new(std::io::ErrorKind::Other, "could not get age!"))
}

fn get_user_name() -> Result<String, io::Error> {
    // Simulating a successful operation
    Ok("Alice".to_string())
}

fn process_user_data() -> Result<(), io::Error> {
    let _ = get_user_name()?;
    let _ = get_user_age()?;
  
    // Processing user data...
    Ok(())
}

fn main() {
    match process_user_data() {
        Ok(()) => println!("User data processed successfully"),
        Err(e) => println!("Error: {}", e),
    }
}

Since get_user_age() encounters an error, the program will terminate and display the following message:

Error: could not get age!

Well, this is all I know about the ? operator. I hope you found this useful.

References



Christoph Voigt
Author
Christoph Voigt
A little bit about you