Announcing Failure

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 a Box<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 both Display and Error, 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!