Errors
Two types of errors:
- will users need to know the details of what went wrong, so that they can handle it as they wish
- will they just log whatever we raise and move on as best they can
Representing Errors
Do we enumerate all possible errors so that the caller can distinguish them or do we provide a single opaque error.
Enumeration
The user needs to be able to distinguish between different error cases so that they can respond accordingly. So we use an enum.
When making our own error types we should take care to:
- Error type should implement the
std::error::Error
trait, which provides callers with common methods for introspecting error types. e.g.Error::source
method a mechanism to find the source of the underlying error. This is most commonly used to trace the error to the source cause. - The type should implement both
Display
andDebug
so that the caller can print meaningful messages. The rule of thumb is a line describing the error for theDisplay
implementation and more descriptive error including information that can help in debugging for theDebug
implementation. - Wherever possible it should implement both
Send
andSync
, so that users can share the errors across thread boundaries. - Wherever possible the error type should be
'static
This allows the user to easily propagate the error up and down the stack without running in to lifetime issues. It also enable the error to be used more easily with type-erased error types.
pub enum FileCopyError {
std::io::Error),
In(std::io::Error),
Out(}
Playground example of error enumeration.
Opaque Errors
Sometime the application using your code can’t meaningfully recover from the error, even if knows exactly the source of the error. In cases like this we want to provide a single opaque error type.
The error should implement Send
, Sync
, Display
, Debug
, and Error
(including the sources method). Internally we might want to represent more fine-grained details, but there is no need to expose those to the users of the library.
Exactly how opaque error type should be is mostly up to us. - it could be a type with all private fields that exposes only limited methods for displaying and introspecting the error - it could be a severely type-erased error type like Box<dyn Error + Send + Sync + 'staic>
, which reveals nothing more than the fact that it is an error and does not generally allow introspection.
Using Box<dyn Error>
leaves users with little choice but to bubble up the error.
Type-erased errors often compose nicely and allow expression of an open-ended errors. We can easily combine errors from different sources without having to introduce additional error types.
e.g. If we write a function whose return is Box<dyn Error + ...>
the we can use ?
across different errors types inside that function, on all sorts of errors and they can all be turned into a common error type.
’static bound in Box<dyn Error ..>
allows
- the propagation of errors without worrying about the lifetime bounds of that method that failed
- access to downcasting
Downcasting
Downcasting is the process of taking an item of one type and casting it to a more specific type.
In the context of errors, downcasting allows a user to tuen a dyn Error
into a concrete underlying error type when the dyn Error
was originally of that type.
The downcast_ref
method returns an Option
which tells the user whether or not the downcast succeed. This method only if the argument is 'static
. If we return an opaque Error that is not 'static
we take away the user’s ability to do this kind of error introspection should they wish.
Propagating Errors
Rust’s ?
operator acts as a shorthand for unwrap of return early when working with Errors.
?
:
- performs type conversion through the
From
trait - in a function that returns
Result<T, E>
we can use?
on anyResult<T, X>
whereE: From<X>
.
This feature makes error erasure through Box<dyn Error>
so appealing. We can just use ?
everywhere without having to worry about the particular error type.
NOTE: The
?
operator is just syntax sugar for a trait tentatively calledTry
. See: https://doc.rust-lang.org/std/ops/trait.Try.html