Failure 1.0.0 on March 15

I’m planning to release a 1.0.0 version of failure on March 15. Once this happens, I don’t plan to release any further breaking changes to the failure crate (though maybe someday in the distant future).

Breaking changes in 1.0

failure is in a somewhat unique position as being a significant part of the public API of other libraries that depend on it. Whether they use the Error struct or derive Fail for a custom error type, this becomes a part of the API they expose to other users. If the ecosystem were to be split between 0.1 and 1.0, there would be two Error types and two Fail traits, and they could not easily interoperate.

Fortunately, there is no pressing need to make a breaking change to any of these public parts of the failure API. For that reason, we’ll be using the dtolnay trick to re-export the same Error type and Fail trait (as well as other parts of the public API part of the crate) type from failure 1.0 in the last version of the 0.1 branch. So even if your dependencies don’t upgrade, but you do (or vice versa), you shouldn’t have to worry about weird breakages you can’t control.

We are, however, making breaking changes to the parts of failure that deal with code generation - the macros and derives. I’ll go through these now.

The bail! macro

The bail! macro added in 0.1.1 had a small but serious flaw, in that it behaves differently from the bail! macro exposed by the error-chain crate. error-chain’s bail! has two functionalities. On the one hand, it can act as a way to create a one-off error through string interpolation. On the other hand, it can act as a way to early return by taking an actual error type.

failure’s bail! was only intended to support the first use case. Unfortunately, attempting to use it for the second case is not a compiler error: it will just convert the error type into its Display string and cast that into an Error. What this means is that if you bail! an error type, you get something that behaves normally except that it loses type information. If you try to downcast later, that will not succeed at runtime.

This was a very unfortunate and silent foot gun that could impact someone upgrading from error-chain to failure. For this reason, in 1.0, the bail! macro has been changed to make using it as an early return mechanism (instead of using it for string errors) is a compile error. Instead, that second use case is supported by a new throw! macro.

The #[cause] attribute

Currently, failure uses an attribute labeled #[cause] to identify the field which is the underlying cause of this error (if there is one). It is considered best practice for all of the attributes used by a derive to be nested under a sort of “namespace” attribute. For this reason, #[cause] will be replaced by #[fail(cause)].

Deriving Display

Currently, the derive for Fail also derives the Display trait as long as you have a #[fail(display)] attribute in the struct. In the final version of failure, failure will export two derives: Fail and Display; unless you derive Display explicitly, an impl of Display will not be generated.

There are three motivations for this change:

  1. There are some evolutions of the derive for the Display we’ve thought about that depend on having an explicit statement that you’re deriving Display.
  2. The derive for Display is orthogonal from deriving Fail and could be useful for other types. The derive will be available outside of failure through the display_derive crate.
  3. I think its just easier to understand what’s happening if you explicitly enumerate every trait impl that is derived.

Changes to the backtrace feature

In 0.1, unless you are no_std, the backtrace feature must be turned on. While this does not generate a backtrace unless you turn on the backtrace environment variable, checking the environment variable does have a cost.

Starting with 1.0, we will no longer turn the backtrace feature on by default. This means that unless you explicitly opt into having backtraces, not even the check for the environment variable will occur when you construct an error.

Libraries are strongly discouraged from having this feature turned on when they publish to crates.io. Only the end user should decide to turn this feature on.

Other new features

We’re working on adding other features to failure, and there’ll be a detailed changelog at 1.0. I want to highlight one in particular, though: a nightly-only optimization to make the Error type smaller.

The small-error feature

The Error type, which uses heap allocation and dynamic dispatch, is designed for cases in which errors are very infrequent. For this reason, it is valuable to avoid pessimizing the happy path by making the Result type overly large. By default, the Error type is the size of two pointers - one to some data in the heap, and one to a vtable. With the small-error feature turned on, the size is cut in half to one pointer.

This works by storing the vtable inline inside of the Error type’s heap representation, instead of storing it next to the heap allocated pointer. The interior of the Error type is then a dynamically sized type, but the pointer to it just a single pointer instead of a wide pointer like trait objects are.

In most use cases that Error would be appropriate, this should improve performance, but by how much remains to be seen. The implementation also relies on nightly features, so its kept under a feature flag.

Beyond 1.0.0

After the 1.0.0 release, I plan to release changes to failure on a six week cadence, just like Rust. If there have been any changes to failure in those six weeks, I’ll push a new minor version. Otherwise, I’ll wait for the next six weeks. I may push bug fixes in between minor versions. There are a number of minor features under discussion now, some of which may make it into 1.0.0 but some of which may not.

1.0.0 will compile on any Rust version 1.18.0 or newer. I have not decided on a stability policy regarding Rust versions yet, but I definitely won’t take it lightly for failure to require a higher version than this (I test that it compiles on 1.18.0 in failure’s CI).

Getting involved

I would love to get more contributors. There are several features users have been requested that I don’t have strong insight into or the time to focus on - especially around the API exposed by Backtrace. I also could really use help fleshing out the documentation of failure, if that’s something you’re interested in.

If there’s any breakage you want to see in failure, now’s the time to make some noise. Note though that the parts of failure that become public dependencies in other libraries are not going to break, limiting what we can break somewhat.