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
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
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.
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.
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.
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
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
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
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:
Display; unless you
Display explicitly, an impl of
Display will not be generated.
There are three motivations for this change:
- There are some evolutions of the derive for the
Displaywe’ve thought about that depend on having an explicit statement that you’re deriving Display.
- The derive for
Displayis orthogonal from deriving
Failand could be useful for other types. The derive will be available outside of failure through the
- I think its just easier to understand what’s happening if you explicitly enumerate every trait impl that is derived.
Changes to the
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.
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.
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).
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
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.