I’m really excited to announce a new crate I’ve been working on, called failure, and which I’ve just released to crates.io. Failure is a Rust library intended to make it easier to manage your error types. This library has been heavily influenced by learnings we gained from previous iterations in our error management story, especially the Error trait and the error-chain crate.
The Fail
trait
The core abstraction in failure is the Fail
trait, a replacement for the
existing std::error::Error
trait.
The original vision of the Error trait was that users would return a
Box<Error>
as their error type, and inside their functions would throw errors
of different types (all of which implement Error
) using the ?
operator.
This plan has run into some snags.
- The
Error
trait did not have any affordances for errors that could contain backtraces. - The
Error
trait did not support downcasting. Once you have aBox<Error>
you cannot downcast it to anything. - The same issue with downcasting applied to the
cause
method, which returned a&Error
. - There was no easy way to implement
Error
; you had to manually implement bothDisplay
andError
, which was a lot of code.
failure fixes these problems by creating a new trait called
Fail
. All error types (or “failures”) ought to implement this
trait. Types that implement the existing Error trait already implement Fail
through a blanket impl.
Deriving Fail
A separate crate, failure_derive
provides a custom derive,
enabling you to derive Fail
and Display
for your type. This derive doesn’t
do anything other than implement these two traits:
extern crate failure;
#[macro_use] extern crate failure_derive;
#[derive(Debug, Fail)]
#[fail(display = "UTF8 error at index `{}`", index)]
pub struct Utf8Error {
index: usize,
}
The Error
type
In addition to the Fail trait, failure also provides a type called Error
,
which any type that implements Fail can be cast into. This can be very
convenient when your function could return many different types of error.
extern crate failure;
use failure::{Error, err_msg};
fn my_function() -> Result<(), Error> {
let stdin = io::stdin();
for line in stdin.lock().lines() {
// throws an io::Error
let line = line?;
if line.chars().all(|c| c.is_whitespace()) {
break
}
if !line.starts_with("$") {
// throws a custom string error
return Err(err_msg("Input did not begin with `$`"));
}
println!("{}", &line[1..]);
}
Ok(())
}
In many ways, the Error type replaces the original Box<Error>
return type.
However, by encapsulating it in the Error
type, we can perform various
optimizations and improvements to keep it as fast as possible. Additionally, it
supports downcasting and guarantees the presence of a backtrace.
Migrating to failure
Because failure is not based on the Error trait, there will be a period of transition from the old system to the new one. failure has been designed with compatibility with the old system in mind.
Applications
Applications interested in using failure are encouraged to migrate immediately.
Most errors implemented with the old system will also implement Fail
,
allowing them to work seemlessly with the new system.
The only quirk is that some applications use a version of error-chain which
produces errors that are not Sync
. If you are using a library which returns
an error like that, you should use the SyncFailure
adapter to add
synchronization to that error. We are working on releasing a new version of
error-chain which resolves this issue.
Libraries
For libraries, upgrading to failure is a breaking change. We encourage libraries interested in using failure to migrate to it during their next breakage.
Even among libraries that do not wish to integrate with failure, we encourage
migrating the new version of error-chain which produces Sync
errors when it
is released.
Conclusion
There is more in depth documentation about failure at this page, including guidance on different patterns you can use for defining errors that arise from the failure crate.
Working on failure has been very exciting, and I’m excited to find out how other peoples’ experience using it turns out! The best way to contribute to failure now is to use it in real code and let us know how that goes for you. Feel free to open issues and leave feedback on the GitHub repository for failure. Thanks!