Rust: Why ? is good

What is the question mark (?) operator?

In Rust, the question mark (?) operator is used as an alternate error propagation method for functions that yield Result or Option types. The ? operator is a shortcut that minimizes the amount of code required in a function to quickly return Err or None from the types Result<T, Err>, or Option.

Error propagation is the process of "propagating," spreading up, or returning error information identified in code that is often triggered by a caller function in order for the caller function to resolve the problem correctly.

Let's have a look at how error propagation works in code using the example below:

fn main() -> Result<()> {
  let i = match halves_if_even(i) {
    Ok(i) => i,
    Err(e) => return Err(e),
  };

  println!("halves of i = {}", i);

  match submit_number(i) {
    Ok(_) => {
      println!("Successfully");
      Ok(())
    },
    Err(e) => return Err(e),
  }
}

fn halves_if_even(i: i32) -> Result<i32, Error> {
  if i % 2 == 0 {
    Ok(i / 2)
  } else {
    Err(/* something */)
  }
}

fn submit_number(i: i32) -> Result<(), Error> {
	// ...
}

However, in that it is very verbose. This is where the question mark operator ? comes in.

fn main() -> Result<()> {
  let i = 35;
  let i = halves_if_even(i)?;
  println!("halves of i = {}", i);
  submit_number(i)?;
  Ok(())
}

fn halves_if_even(i: i32) -> Result<i32, Error> {
  // ...
}

fn submit_number(i: i32) -> Result<(), Error> {
  // ...
}

What ? does here is equivalent to the match statement above with an addition. This operator desugars into a pattern match.

The nice thing about this code is that one can easily see and audit potential errors: for example, I can see that halves_if_even may result in an error, and a keen-eyed reviewer may see the potential data loss.

Even better, in the event of an error, I can do some type of recovery by opting to match rather than forward the error.

fn main() -> Result<()> {
  let i = 35;
  let i = halves_if_even(i)?;

  match submit_number(i) {
    Ok(_) => Ok(()),
    Err(err) => recover_from_error(err),
  }
}

References

Rust 🦀RustCargo