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:
- 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. - The derive for
Display
is orthogonal from derivingFail
and could be useful for other types. The derive will be available outside of failure through thedisplay_derive
crate. - 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.