Result and Error
Using a Result
is the primary way of handling errors in Rust. A Result
is a generic enum in the standard library, it has two variants: Ok
and Err
, which indicate correct execution and incorrect execution, respectively. Both Result
and its two variants are in the prelude, so you don't need to explicitly import them and you don't have to write Result::Ok
as you would for most enums.
Both variants take a single argument which can be any type. You'll usually see Result<T, E>
where T
is the type of value returned in Ok
and E
is the error type returned in Err
. It's fairly common for modules to use a single error type for the whole module and to define an alias like pub type Result<T> = std::result::Result<T, MyError>;
. In that case, you'll see Result<T>
as the type, where MyError
is an implicit error type.
Creating a Result
value is as simple as using the variant as a constructor, just like other enums. E.g, for a Result<i32, i32>
(a Result
with an i32
payload and an i32
error code), you would use Ok(42)
and Err(2)
to create Result
values.
When receiving a Result
object, you can address the ok and error cases by using a match
expression, e.g.,
fn foo(r: Result<String, MyError>) {
match r {
Ok(s) => println!("foo got a string: {s}"),
Err(e) => println!("an error occurred: {e}"),
}
}
There are more ergonomic ways to handle Result
s too. There are a whole load of combinator methods on Result
. See the docs for details. There are way too many to cover here, but as an example, map
takes a function and applies it to the payload of the result if the result is Ok
and does nothing if it is Err
, e.g.,
fn foo(r: Result<i32, MyError>) -> Result<String, MyError> {
r.map(|i| i.to_string())
}
The ?
operator
Applying ?
to a result will either unwrap the payload, if the result is an Ok
, or immediately return the error if it is an Err
. E.g.,
fn foo(r: Result<i32, MyError>) -> Result<String, MyError> {
let i = r?; // Unwraps Ok, returns an Err
Ok(i.to_string())
}
The above code does the same as the previous example. In this case, the map
code is more idiomatic, but if we had a lot of work to do with i
, then using ?
would be better. Note that the type of i
is i32
in both examples.
The ?
operator can be used with other types too, notably Option
. You can also use it with your own types, see the discussion of the Try
trait, below.
?
does not have to just return the error type directly. It can convert the error type into another by calling From::from
. So if you have two error types: Error1
and Error2
and Error2
implements From<Error1>
, then the following will work (note the different error types in the signature):
fn foo(r: Result<i32, Error1>) -> Result<String, Error2> {
let i = r?;
// ...
}
This implicit conversion is relied on for several patterns of error handling we'll see in later chapters.
Try blocks
Try blocks are an unstable language feature (#![feature(try_blocks)]
). The syntax is try { ... }
where the block contains code like any other block. The difference compared to a regular block, is that a try block introduces a scope for the question mark operator. If you use ?
inside a try block, it will propagate the error to the result of the block immediately, rather than returning the error from the current function.
For example,
#![allow(unused)] fn main() { let result: Result<i32, ParseIntError> = try { "1".parse::<i32>()? + "foo".parse::<i32>()? + "3".parse::<i32>()? }; }
At runtime, execution will stop after the second parse
call and the value of result
will be the error from that call. Execution will continue after the let
statement, without returning.
Option
Option
is similar to Result
in that it is a very common, two-variant enum type with a payload type Some
(compare to Result::Ok
). The difference is that Option::None
(compared with Result::Err
) does not carry a payload. It is, therefore, analogous to Result<T, ()>
and there are many methods for converting between Option
and Result
. ?
works with Option
just like Result
.
The intended meaning of the types, however, is different. Result
represents a value which has either been correctly computed or where an error occurred in its computation. Option
represents a value which may or may not be present. Generally, Option
should not be used for error handling. You may use or come across types like Result<Option<i32>, MyError>
, this type might be returned where the computation is fallible, and if succeeds it will return either an i32
or nothing (but importantly, returning nothing is not an error).
The Error
trait
The error trait, std::error::Error
, is a trait for error types. There is no hard requirement, you can use a type in a Result
and with ?
without implementing Error
. It has some useful functionality, and it means you can use dynamic error handling using dyn Error
types. Generally you should implement Error
for your error types (there are no required methods, so doing so is easy); most error libraries will do this for you.
Provided functionality
Display
is a super-trait of Error
which means that you can always convert an error into a user-facing string. Most error libraries let you derive the Display
impl using an attribute and a custom format string.
The Error
trait has an experimental mechanism for attaching and retrieving arbitrary data to/from errors. This mechanism is type-driven: you provide and request data based on its type. You use request
methods (e.g., request_ref
) to request data and the provide
method to provide data, either by value or by reference. You can use this mechanism to attach extra context to your errors in a uniform way.
A common usage of this mechanism is for backtraces. A backtrace is a record of the call stack when an error occurs. It has type Backtrace
which has various methods for iterating over the backtrace and capturing the backtrace when an error occurs. To get a backtrace, use the Error::request_ref
method, e.g., if let Some(trace) = err.request_ref::<Backtrace>() { ... }
.
For an error to support backtraces, it must capture a backtrace when the error occurs and store it as a field. The error must then override the Error::provide
method to provide the backtrace if it is requested. For more details on this mechanism see the docs of the Provider
trait which is used behind the scenes to implement it. (Note that there used to be a specific method for getting a backtrace from an error and this has been replaced by the generic mechanism described here).
Error::source
is a way to access a lower-level cause of an error. For example, if your error type is an enum MyError
with a variant Io(std::io::Error)
, then you could implement source
to return the nested io::Error
. With deep nesting, you can imagine a chain of these source errors, and Error
provides a sources
method1 to get an iterator over this chain of source errors.
This and some other methods are implemented on dyn Error
, rather than in the trait itself. That makes these methods usable on trait objects (which wouldn't be possible otherwise due to generics, etc.), but means they are only usable on trait objects. That reflects the expected use of these methods with the dynamic style of error handling (see the following section).
Dynamic error handling
When using Result
you can specify the concrete error type, or you can use a trait object, e.g., Result<T, Box<dyn Error>>
. We'll talk about the pros and cons of these approaches in later chapters on designing error handling, for now we'll just explain how it works.
To make this work, you implement Error
for your concrete error types, and ensure they don't have any borrowed data (i.e., they have a 'static
bound). For ease of use, you'll want to provide a constructor which returns the abstract type (e.g., Box<dyn Error>
) rather than the concrete type. Creating and propagating errors works the same way as using concrete error types.
Handling errors might be possible using the abstract type only (using the Display
impl, source
and sources
methods, and any other context), or you can downcast the error trait object to a concrete type (using one of the downcast
methods). Usually, there are many possibilities for the concrete type of the error, you can either try downcasting to each possible type (the methods return Option
or Result
which facilitates this) or use the is
method to test for the concrete type. This technique is an alternative to the common match
destructuring of concrete errors.
Evolution of the Error
trait
Error handling in general, and the Error
trait in particular, have been evolving for a long time and are still in flux. Much of what is described above is nightly only and many unstable features have changed, and several stable ones deprecated. If you're targetting stable Rust, it is best to mostly avoid using the Error
trait and instead use an ecosystem alternative like Anyhow. You should still implement Error
though, since the trait itself is stable and this facilitates users of your code to choose the dynamic path if they want. You should also be conscious when reading docs/StackOverflow/blog posts that things may have changed.
The Try
trait
The ?
operator and try blocks, and their semantics are not hard-wired to Result
and Option
. They are tied to the Try
trait, which means they can work with any type, including a custom alternative to Result
. We don't recommend using your own custom Result
, we have in fact never found a good use case (even when implementing an error handling library or in other advanced cases) and it will make your code much less compatible with the ecosystem. Probably, the reason you might want to implement the Try
trait is for non-error related types which you want to support ergonomic short-circuiting behaviour using ?
(e.g., Poll
; although because of the implied semantics of error handling around ?
, this might also be a bad idea). Anyway, it's a kinda complex trait and we're not going to dive into it here, see the docs if you're interested.
Deprecated stuff
There is a deprecated macro, try
, which does basically the same thing as ?
. It only works with Result
and in editions since 2018, you have to use r#try
syntax to name it. It is deprecated and there is no reason to use it rather than ?
; you might come across it in old code though.
There are some deprecated methods on the Error
trait. Again, there is no reason to use these, but you might see them in older code. Error::description
has been replaced by using a Display
impl. Error::cause
has been replaced by Error:source
, which has an additional 'static
bound on the return type (which allows it to be downcast to a concrete error type).