Where the ? Operator Can Be Used
The ?
operator can only be used in functions whose return type is compatible with the value the ?
is used on. This is because the ?
operator is defined to perform an early return of a value out of the function, in the same manner as the match
expression we defined in Listing 9-6. In Listing 9-6, the match
was using a Result
value, and the early return arm returned an Err(e)
value. The return type of the function has to be a Result
so that it's compatible with this return
.
In Listing 9-10, let's look at the error we'll get if we use the ?
operator in a main
function with a return type that is incompatible with the type of the value we use ?
on.
Filename: src/main.rs
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")?;
}
Listing 9-10: Attempting to use the ?
in the main
function that returns ()
won't compile.
This code opens a file, which might fail. The ?
operator follows the Result
value returned by File::open
, but this main
function has the return type of ()
, not Result
. When we compile this code, we get the following error message:
error[E0277]: the `?` operator can only be used in a function that returns
`Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:48
|
3 | / fn main() {
4 | | let greeting_file = File::open("hello.txt")?;
| | ^ cannot use the `?`
operator in a function that returns `()`
5 | | }
| |_- this function should return `Result` or `Option` to accept `?`
|
= help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not
implemented for `()`
This error points out that we're only allowed to use the ?
operator in a function that returns Result
, Option
, or another type that implements FromResidual
.
To fix the error, you have two choices. One choice is to change the return type of your function to be compatible with the value you're using the ?
operator on as long as you have no restrictions preventing that. The other choice is to use a match
or one of the Result<T, E>
methods to handle the Result<T, E>
in whatever way is appropriate.
The error message also mentioned that ?
can be used with Option<T>
values as well. As with using ?
on Result
, you can only use ?
on Option
in a function that returns an Option
. The behavior of the ?
operator when called on an Option<T>
is similar to its behavior when called on a Result<T, E>
: if the value is None
, the None
will be returned early from the function at that point. If the value is Some
, the value inside the Some
is the resultant value of the expression, and the function continues. Listing 9-11 has an example of a function that finds the last character of the first line in the given text.
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
Listing 9-11: Using the ?
operator on an Option<T>
value
This function returns Option<char>
because it's possible that there is a character there, but it's also possible that there isn't. This code takes the text
string slice argument and calls the lines
method on it, which returns an iterator over the lines in the string. Because this function wants to examine the first line, it calls next
on the iterator to get the first value from the iterator. If text
is the empty string, this call to next
will return None
, in which case we use ?
to stop and return None
from last_char_of_first_line
. If text
is not the empty string, next
will return a Some
value containing a string slice of the first line in text
.
The ?
extracts the string slice, and we can call chars
on that string slice to get an iterator of its characters. We're interested in the last character in this first line, so we call last
to return the last item in the iterator. This is an Option
because it's possible that the first line is the empty string; for example, if text
starts with a blank line but has characters on other lines, as in "\nhi"
. However, if there is a last character on the first line, it will be returned in the Some
variant. The ?
operator in the middle gives us a concise way to express this logic, allowing us to implement the function in one line. If we couldn't use the ?
operator on Option
, we'd have to implement this logic using more method calls or a match
expression.
Note that you can use the ?
operator on a Result
in a function that returns Result
, and you can use the ?
operator on an Option
in a function that returns Option
, but you can't mix and match. The ?
operator won't automatically convert a Result
to an Option
or vice versa; in those cases, you can use methods like the ok
method on Result
or the ok_or
method on Option
to do the conversion explicitly.
So far, all the main
functions we've used return ()
. The main
function is special because it's the entry point and exit point of an executable program, and there are restrictions on what its return type can be for the program to behave as expected.
Luckily, main
can also return a Result<(), E>
. Listing 9-12 has the code from Listing 9-10, but we've changed the return type of main
to be Result<(), Box<dyn Error>>
and added a return value Ok(())
to the end. This code will now compile.
Filename: src/main.rs
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}
Listing 9-12: Changing main
to return Result<(), E>
allows the use of the ?
operator on Result
values.
The Box<dyn Error>
type is a trait object, which we'll talk about in "Using Trait Objects That Allow for Values of Different Types". For now, you can read Box<dyn Error>
to mean "any kind of error." Using ?
on a Result
value in a main
function with the error type Box<dyn Error>
is allowed because it allows any Err
value to be returned early. Even though the body of this main
function will only ever return errors of type std::io::Error
, by specifying Box<dyn Error>
, this signature will continue to be correct even if more code that returns other errors is added to the body of main
.
When a main
function returns a Result<(), E>
, the executable will exit with a value of 0
if main
returns Ok(())
and will exit with a nonzero value if main
returns an Err
value. Executables written in C return integers when they exit: programs that exit successfully return the integer 0
, and programs that error return some integer other than 0
. Rust also returns integers from executables to be compatible with this convention.
The main
function may return any types that implement the std::process::Termination
trait, which contains a function report
that returns an ExitCode
. Consult the standard library documentation for more information on implementing the Termination
trait for your own types.
Now that we've discussed the details of calling panic!
or returning Result
, let's return to the topic of how to decide which is appropriate to use in which cases.