Skip to main content
  1. 📝 Notes/

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

· loading ·

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