The ? operator in Rust

The ? operator in Rust

Photo by Evan Dennis on Unsplash

Since Rust does not have exceptions, it does not support exception handling in the traditional sense.

It does provide a macro !panic, which can be used to exit out of the program prematurely but it is recommended to be used only for errors that are truly unexpected.

For everything else, Rust uses the following 2 enums:

  1. Option

  2. Result

The structure of the enums is as follows:

// An output can have either Some value or None value.
enum Option<T> { // T is a generic and it can contain any type of value.
    Some(T),
    None,
}

// A result can represent either Ok or Err.
enum Result<T, E> { // T and E are generics. T can contain any type of value, E can be any error.
    Ok(T),
    Err(E),
}

Option can either return Some(type) value or None.

Result can either return Ok(value) or Err(any error).

If you're wondering why there are 2 constructs for the same purpose, the difference between them is that in Result, you can explicitly specify the error message.

The following code snippet is an example of using the Option enum: (Result can also be used in a similar manner.)

fn main() {
    println!("{:?}", api_call())
}

// This function will either return None or str.
fn api_call() -> Option<&'static str> {
    let result = executing_impure_function();

    // The `match` is similar to `switch case` in other languages.
    match result {
        Some(obj) => println!("{:?}", obj),
        None => println!("Failed.")
    } 

    return result
}

fn executing_impure_function() -> Option<&'static str> {

    //if the optional value is not empty
    if true {
        return Some("success.");
    }

    //else
    None
}
------------------------------------------------------------------------------
// Output if executing_impure_function() uses `true` in if condition.
"success."      
Some("success.")

// Output if executing_impure_function() uses `false` in if condition.
Failed.
None

The ? operator

This operator is used as a suffix to a function that will return either a Result or an Option.

If the function returns Some(value) or Ok(value), then the value is used as the return value.

But if the function returns None or Err, the value is immediately returned to the caller of the function. In this case, any code which follows this function call will not be executed.

To better understand this, let us take the code above and tweak it to use the ? operator.

fn main() {
    println!("{:?}", api_call())
}

fn api_call() -> Option<&'static str> {
    let result = executing_impure_function()?; // if None, returns immidiately;

    println!("printing inside the api_call() function: {:?}", result);

    return Some("lorem ipsum")
}

fn executing_impure_function() -> Option<&'static str> {

    //if the optional value is not empty
    if true {
        return Some("success.");
    }

    //else
    return None
}
------------------------------------------------------------------------------
// Output if executing_impure_function() uses `true` in if condition.
printing inside the api_call() function: "success."
Some("lorem ipsum")

// Output if executing_impure_function() uses `false` in if condition.
None

You can see that in the scenario where executing_impure_function returns None, The print statement inside the api_call function doesn't get executed and None is immediately returned to the caller.

Whereas in the event executing_impure_function returns a non-None value, the variable result gets assigned the returned value and the rest of the function is executed.

This is very useful to know since you can suffix potentially unsafe function calls with ?, which would caution future developers about it.