NickyMeulemanNime
Metadata
  • Date

  • By

    • Nicky Meuleman
  • Tagged

  • Older post

    Rust: stack vs heap

  • Newer post

    Rust: tuple vs array

Rust syntax: what the questionmark?

Ever seen a line of Rust code with a rising inflection, with an inquisitive tone?
That line is questioning its existence

let f = File::open("username.txt")?;

The ? in Rust is the questionmark operator.

It’s a piece of syntax that replaces a very common case of error handling.

The following piece of code deals with 2 operations that can fail. It tries to open a file and read the username inside.

If any of the operations fail, it will stop execution of the function and return information about that failure. This usecase is called error propagation.

fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("username.txt");
let mut f = match f {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}

The read_username_from_file function returns a Result<String, io::Error>. That means the returned value from that function will either be an Ok that holds a String, or an Err that holds an instance of io::Error.

Inside read_username_from_file there is a call to File::open, which returns a Result type.

  • It can return an Ok with a file handle inside.
  • It can return an Err with an error inside (an io::Error).

To deal with that possibility, the code calls a match on the result of the File::open function.

  • On Ok, it returns what was inside the Ok from the match
  • On Err, it early returns from the entire function with that Err as returned value.

To put it differently: it either unwraps the Ok, or it quits the function and returns the Err.

Later in the read_username_from_file function another operation that returns a Result is called. Very similar logic is applied there.

  • If the returned value is an Ok, this signals a successful completion of the function. s is wrapped in an Ok and returned from the match.
  • If the returned value is an Err, it is returned from the match. (the Err contains an io::Error)

Since this is the last expression of the function, the return keyword is no longer needed as the returned value from the match is also the returned value from the function.

Using ?, the function now looks like this:

fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}

The ? operator can be used on a Result type if the function it’s in returns a Result as well.

Every time you see a ?, that’s a possible early return from the function the ? is used in.

The ? is a shorthand for the behaviour in that first match statement.

  • on Ok, it unwraps the Ok and evaluates to the value inside
  • on Err, it returns from the function with that Err (that holds an io::Error in this case)

The possible Err from File::open is returned from the function.
If there is no error, f will hold the file handle the Ok contained and execution of the function continues.

The possible Err from read_to_string is returned from the function.
If there is no error, the method finished successfully and execution of the function continues.
The value the Ok held is ignored by turning the f.read_to_string(&mut s)? expression into a statement.
If the code reaches a point after that line, it means the result was Ok.

The last expression in this rewritten function is an Ok with a String inside: Ok(s).

When using the ? on a Result, I call it the Annie operator

Designed and developed by Nicky Meuleman

Built with Gatsby. Hosted on Netlify.