Defining your own error type
When starting a new project, defining your own error type up-front is a good thing to do.
It will result in more ergonomic code, as you can return the same error type from most
functions and can get it to work with the ?
operator. This is especially important if
you use external crates and want to ‘amalgamate’ their error types.
The strategy defined here is the TL;DR summary of a blog post by Rustmeister BurntSushi. It doesn’t use any external ‘error helper’ crates.
Step 1 - define a custom error type
This type is a union of all possible error types your program needs to handle:
#[derive(Debug)]
pub enum MyErrorType {
// Errors from external libraries...
Io(io::Error),
Git(git2::Error),
// Errors raised by us...
Regular(ErrorKind),
Custom(String)
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum ErrorKind {
NotFound,
NotAuthorized,
// etc
impl ErrorKind {
fn as_str(&self) -> &str {
match *self {
ErrorKind::NotFound => "not found",
ErrorKind::NotAuthorized => "not authorized"
}
}
}
This ‘kind enum’ design is used by the Rust standard IO library.
The as_str
will help in the next step.
Step 2 - Implement ‘Error’ and ‘Display’ traits
Unfortunately Error
is limited to returning a static string constant:
impl Error for MyErrorType {
fn description(&self) -> &str {
match *self {
MyErrorType::Io(ref err) => err.description(),
MyErrorType::Git(ref err) => err.description(),
MyErrorType::Regular(ref err) => err.as_str(),
MyErrorType::Custom(ref err) => err,
}
}
}
fmt::Display
is more flexible:
impl fmt::Display for MyErrorType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
MyErrorType::Io(ref err) => err.fmt(f),
MyErrorType::Git(ref err) => err.fmt(f),
MyErrorType::Regular(ref err) => write!(f, "A regular error occurred {:?}", err),
MyErrorType::Custom(ref err) => write!(f, "A custom error occurred {:?}", err),
}
}
}
Step 3 - Implement ‘From’ for the external error types
This enables the ?
operator:
impl From<io::Error> for MyErrorType {
fn from(err: io::Error) -> MyErrorType {
MyErrorType::Io(err)
}
}
impl From<git2::Error> for MyErrorType {
fn from(err: git2::Error) -> MyErrorType {
MyErrorType::Git(err)
}
}
Step 4 - Optional - Create a ‘Result<T, E>’ alias
pub type Result<T> = std::result::Result<T, MyErrorType>;
This does nothing other than simplify (some would say make more opaque) your function signatures.
Step 5 - Use it!
fn some_func() -> Result<usize> {
// Possible this will generate a std::io::Error.
let _f = std::fs::File::create("aa")?;
// Possible this will generate a git2::Error.
let _g = Repository::init("/path/to/a/repo")?;
// Return one of my one errors.
Err(MyErrorType::Regular(ErrorKind::NotAuthorized))
}