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 (anio::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 theOk
from thematch
- On
Err
, it early returns from the entire function with thatErr
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 anOk
and returned from thematch
. - If the returned value is an
Err
, it is returned from thematch
. (theErr
contains anio::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 theOk
and evaluates to the value inside - on
Err
, it returns from the function with thatErr
(that holds anio::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