Rust proper error handling (auto convert from one error type to another with question mark)
Solution 1:
Update 2020
The rust programming language is evolving quickly so a new answer can be added! I really liked custom_error but now I think thiserror
will be my loved one!
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataStoreError {
#[error("data store disconnected")]
Disconnect(#[from] io::Error),
#[error("the data for key `{0}` is not available")]
Redaction(String),
#[error("invalid header (expected {expected:?}, found {found:?})")]
InvalidHeader {
expected: String,
found: String,
},
#[error("unknown data store error")]
Unknown,
}
This allow change io::Error
to DataStoreError::Disconnect
with question mark ?
. Go here for details
useful links:
- great blog about using
thiserror
in combine withanyhow
Other interesting crates:
- anyhow - Flexible concrete Error type built on std::error::Error
- snafu - Situation Normal: All Fouled Up - SNAFU is a library to easily assign underlying errors into domain-specific errors while adding context. (similar to thiserror)
- custom_error - This crate contains a macro that should make it easier to define custom errors without having to write a lot of boilerplate code.
for panics:
- proc-macro-error - This crate aims to make error reporting in proc-macros simple and easy to use.
- human-panic - Panic messages for humans. Handles panics by calling std::panic::set_hook to make errors nice for humans.
Solution 2:
Unfortunately, in your case you cannot create a reqwest::Error
from other error types, if the reqwest
library does not provide a way to do so (and it likely doesn't). To solve this problem, which is very common, especially in applications which use multiple libraries, the proper solution would be one of the following:
-
Declare your own custom enum with all errors your application works with (or one subsystem of your application; granularity highly depends on the project), and declare
From
conversions from all errors you work with to this enum type.As an extension of this approach, you can use
error-chain
(orquick-error
, on which error-chain is basically based) to generate such custom types and conversions in a semi-automatic way. -
Use a special, generic error type. There are basically two of them:
a.
Box<Error>
whereError
is defined in the standard library.b. Use the
Error
type defined in thefailure
crate.Then the question mark operator will be able to convert any compatible error to one of these types because of various
Into
andFrom
trait implementations.
Note that the failure
crate is intended to be the way to define errors promoted in the Rust community. Not only does it provide a common error type and trait (which fixes various issues with the std::error::Error
trait; see for example here), it also has facilities to define your own error types (for example, with failure_derive
), and for tracking error context, causes and generating backtrace. Additionally, it tries to be as compatible with the existing error handling approaches as possible, therefore it can be used to integrate with libraries which use other, older approaches (std::error::Error
, error-chain
, quick-error
) quite easily. So I strongly suggest you to consider using this crate first, before other options.
I have already started using failure
in my application projects, and I just can't express how much easier and nicer error handling has become. My approach is as follows:
-
Define the
Result
type:type Result<T> = std::result::Result<T, failure::Error>;
Use
Result<Something>
everywhere where an error can be returned, using the question mark operator (?
) to convert between errors and functions likeerr_msg
orformat_err!
orbail!
to create my own error messages.
I have yet to write a library using failure
, but I imagine that for libraries it would be important to create more specific errors declared as an enum, which can be done with the failure_derive
crate. For applications, though, the failure::Error
type is more than enough.
Solution 3:
In that case, reusing the underlying error type is not possible because you cannot construct its hidden fields. And even when it is possible, I would advise against it, in order to make your code more flexible and future-proof.
Defining custom error types can involve writing a lot of boilerplate, but fortunately several libraries exist to alleviate this pain. failure, error-chain and quick-error were already mentioned above, but I would like to point you to a crate I wrote that involves even less boilerplate than the others : custom_error. With it, you can write:
#[macro_use] extern crate custom_error;
custom_error!{ MyError
Request{source: reqwest::Error} = "request error",
Url{source: url::ParseError} = "invalid url"
}
Solution 4:
As already stated by Vladimir Matveev, the failure crate should be your starting point. Here is my solution:
use std::io;
use std::result;
use failure::{Backtrace, Fail};
/// This is a new error type manged by Oxide library.
/// The custom derive for Fail derives an impl of both Fail and Display.
#[derive(Debug, Fail)]
pub enum OxideError {
#[fail(display = "{}", message)]
GeneralError { message: String },
#[fail(display = "{}", message)]
IoError {
message: String,
backtrace: Backtrace,
#[cause]
cause: io::Error,
},
}
/// Create general error
pub fn general(fault: &str) -> OxideError {
OxideError::GeneralError {
message: String::from(fault),
}
}
/// Create I/O error with cause and backtrace
pub fn io(fault: &str, error: io::Error) -> OxideError {
OxideError::IoError {
message: String::from(fault),
backtrace: Backtrace::new(),
cause: error,
}
}
This error enumeration is extendible which allows it to accommodate future modifications that might be made to the program.