Welcome to module 4
on returning Result
and using ?.
In this module, we're going to cover
how to return Result values from
your own functions that might fail.
Then, we'll discuss how to use the ?
operator within functions that return
Result to propagate errors in
a concise way.
Understanding what the ? operator
does will help you to understand where
you can and can't use it.
And we'll round out the module
with some recent features added to Rust
and make the ? operator useful
in more contexts.
Let's start with how to return a Result.
Imagine we're implementing the back end
of a Twitter-like website where people
can post statuses containing up to
200 bytes of text each.
If a user of our site tries
to send a status containing more
than 200 bytes of text,
we should return an error.
We're going to write the part of this web app
that's the function named save_status.
This function takes the content of a status,
checks that there are fewer than 200 bytes of
text, and saves it to the database.
If those operations succeed,
we'll return the ID of the new status'
database record.
If those operations fail,
we'll return an error describing what went
wrong.
The save_status function signature
will have a parameter
named text of type string
slice.
The return type will be a Result
and then we have to specify the types that
will be returned in the success case
and the failure case within angle brackets.
Because, in the success case, we want
to return an ID, the first
type in the angle brackets will be i64 -
the type our database
uses for the id field.
For the error case, we're going to keep
this example simple
and return a hardcoded string literal
that describes what went wrong.
Therefore, we specify 'static str
slice as the second type
in the angle brackets.
We'll be talking more about lifetimes
in the next unit.
All you need to know right now is
that 'static means a reference
that stays valid for the entire duration of
the program, which hardcoded
string literals are.
In the body of the function,
we want to test the number of bytes
and the status,
and if it's over our limit of 200,
return the Err variant of the Result
enum containing a hardcoded
string describing the problem.
To finish writing this function,
let's look at a situation where we
want to propagate an error that
will illustrate how to use the ?
operator.
In the next part of the save_status
function,
we want to save the status text to the
database.
Imagine we have a function provided
by a library for saving to
the database that takes a string slice
and returns a Result that will contain
an instance of a StatusRecord struct
if it succeeds and a hardcoded
error message if it fails.
In save_status, we're going to
call the save_to_database function
and handle the returned Result
with a match expression, as we did in
the previous module.
If we match the Ok
variant, we want to extract the
inner status record
and save it in the variable record.
Once we have the record, we
can get the id value
and return that from save_status wrapped
in an Ok.
If save_to_database fails,
we don't want to do anything
with the Err; we just want
to stop what we're doing in the save_status
function
and return early
with the error for whatever code is calling
save_status to handle.
This pattern of extracting a success
value and propagating an Err
value is so common in Rust
that the ? operator was added
to have this behavior.
We can replace this match expression
with only a call to the save_to_database
function followed by a ?
,
and this code will have the same behavior
written in a much more concise way.
There's a bit of automatic conversion
that ? does for you as well,
which we'll discuss in the future module
on custom error types.
Now the save_status function is
complete
and this code compiles successfully.
As an aside, before the ?
operator was added to the language,
it was implemented as a macro called
try!.
If you're reading old code
and see calls to the try! macro,
know it serves the same purpose
as ?.
Now that we've covered what ?
does, let's talk about
where we can use ?.
If we try to use ?
in a function like this
add_five function that returns
i32,
we'll get an error that says the `?`
operator can only be used in
a function that returns `Result`.
This is because of the early return
of an Err that the question mark
does, as we saw in
the last example.
The signature of the function in which
we use the ? operator
needs to allow for the Err variant
to be returned.
So, it needs to have a return type
that matches what we're using ?
on.
In the add_five example,
if we wanted to be able to use ?
here, we can change the return
type to be Result<i32
ParseIntError
and change what we return to be wrapped
in an Ok variant.
There have been some improvements to the ?
operator recently.
Let's talk about those briefly.
When the ? was introduced,
you could only use it on instances
of the Result enum.
As of Rust 1.22,
you can use ? on Option values
as well.
Here's an example using ?
on Option.
The behavior of ? an Option
is that it extracts a value from the
Some variant
and returns a None variant early,
if it finds one.
This function will return a Some
variant containing the last number
plus 5,
if there is a last number in list.
If there isn't, this function
will return a None variant.
The same restrictions apply.
Using ? on an Option
value must be done within
a function that also returns Option.
The next improvement was added in
Rust 1.26 and
enabled using ? to return
Result values in main.
The main function is a special function
that is the entry point for a binary.
Before Rust 1.26,
main couldn't return a value.
So, attempting to use ?
in the main function would result
in the same error
we saw before about only
being able to use ?
in functions that return Result.
There was no way of fixing this error
other than using a match expression
to handle the result instead of ?.
In Rust 1.26
and later, you can add a Result
return type to main, plus
return an Ok variant at the end
of main
and this enables the use of the ?
operator in main on results
only.
In Rust 1.28
and later, test functions have
the ability to have a Result return type
in a similar way so that you
can use ? in tests too.
In this module,
we discussed how to return the Result
type from your own functions by
choosing and specifying a Success
value type
and an Error value type.
We looked at the ? operator
and how it propagates errors.
We discussed how ? can only
be used in functions that return the type
? is called on,
and we went over recent improvements
that allow using the ?
with the Option type, in main,
and in tests.
In the next module, we'll go
over the advantages your programs
gain from Rust's error handling strategy.