Introducing customizable outputs, related errors, multiple sources, and much more to error handling in Rust
October 3rd, 2022
This is an old blog post and may contain code which does not compile with the
current error-stack
version. Please check out the latest
documentation and
examples
for more information.
Back in June we announced the initial public release of error-stack
, our context-aware Rust error library. We developed this to help address a lot of challenges we encountered when implementing error handling in our simulation engine. In the time since then we’ve started using it in our entity-graph query layer that we’re building as a datastore for HASH. Across that development we’ve learned a lot of lessons, and in joint efforts with open-source contributors we’re ready to release a new version of the library.
Since our last release we’ve been fixing user-reported bugs, reviewing open-source contributor’s pull requests, and adding new features. Version 0.2 is available now and includes important changes from std
, a completely redesigned output with new customization options, support for related errors and multiple error sources, compatibility with anyhow
and eyre
, and a number of other improvements.
std
in v0.2Provider
API has landed and replaces our vendored-in implementation.Backtrace
was stabilized with Rust 1.65(🎉): we now support Backtrace
s on a non-nightly channel starting with 1.65-beta.Backtrace
is captured. Otherwise, backtraces are captured automatically when RUST_BACKTRACE
or RUST_LIB_BACKTRACE
env-vars are set.Error
types now implement Provider
. The blanket implementation on Context
will re-use the Error::provide
values.Error
is now available in core::error
, for nightly channels.The output in v0.2 has been completely redesigned by Bilal Mahmoud (@indietyp) — huge kudos! It’s now much easier to read compared to v0.1, with visual cues to represent the information hierarchy at a glance, and more information is provided.
error-stack v0.2:
Error: experiment error: could not run experiment
├╴examples/demo.rs:51:18
├╴unable to set up experiments
│
├─▶ invalid experiment description
│ ├╴examples/demo.rs:21:10
│ ╰╴experiment 2 could not be parsed
│
╰─▶ invalid digit found in string
├╴examples/demo.rs:19:10
╰╴"3o" could not be parsed as experiment
error-stack v0.1:
Error: experiment error: could not run experiment
at examples/demo.rs:54:18
- unable to setup experiments
Caused by:
0: invalid experiment description
at examples/demo.rs:24:10
- experiment 2 could not be parsed
1: invalid digit found in string
at examples/demo.rs:22:10
- "3o" could not be parsed as experiment
To enable granular control over the output format, hooks are now set for specific types instead of for the whole Report
. Values provided through Context::provide
can be printed, along with attachments, by defining a hook for their types. And it’s now possible to selectively define outputs per attachment. For example:
// Note: neither `Debug`, nor `Display` is implemented
struct Suggestion(&'static str);
Report::install_debug_hook::<Suggestion>(|value, context| {
context.push_body(format!("suggestion: {}", value.0));
});
let report = read("config.txt").attach(Suggestion("check if `config.txt` exists"));
no such file or directory
├╴read.rs:10:5
╰╴suggestion: check if `config.txt` exists
Report outputs now consist of two sections: a Body
and an Appendix
. The Body
provides top-level information while the Appendix
includes additional, more verbose details, like a backtrace. Hooks can be used to write to both of these sections.
experiment error: could not run experiment
├╴examples/demo.rs:67:17
├╴backtrace with 19 frames (1)
├╴experiment 3 has no valid description
╰╴unable to set up experiments
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Backtrace No. 1
0: std::backtrace_rs::backtrace::libunwind::trace
at /rustc/8b705839cd656d202e920efa8769cbe43a5ee269/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
1: std::backtrace_rs::backtrace::trace_unsynchronized
at /rustc/8b705839cd656d202e920efa8769cbe43a5ee269/library/std/src/../../backtrace/src/backtrace/mod.rs:66:5
2: std::backtrace::Backtrace::create
at /rustc/8b705839cd656d202e920efa8769cbe43a5ee269/library/std/src/backtrace.rs:333:13
3: core::ops::function::FnOnce::call_once
at /rustc/8b705839cd656d202e920efa8769cbe43a5ee269/library/core/src/ops/function.rs:251:5
...
18: _main
For a full guide to using hooks, please refer to the fmt
module documentation.
error-stack
v0.2 introduces support for related errors and multiple error sources, which can be invaluable when multiple errors occur at the same time, e.g. in loops or in multi-threaded applications. Report
s are no longer a linear stream of events. You can now create a tree of different Report
s, enabling the capture of multiple simultaneous errors without losing information.
This is also shown in the output:
Error: experiment error: could not run experiment
├╴examples/demo.rs:73:18
├╴Unable to set up experiments
│
├─▶ invalid experiment description
│ ├╴examples/demo.rs:43:10
│ ╰╴experiment 2 could not be parsed
│
╰┬▶ invalid digit found in string
│ ├╴examples/demo.rs:25:18
│ ╰╴"3o" could not be parsed as experiment
│
╰▶ invalid digit found in string
├╴examples/demo.rs:25:18
╰╴"4a" could not be parsed as experiment
experiment error: could not run experiment
├╴examples/demo.rs:67:17
├╴experiment 3 has no valid description
╰╴unable to set up experiments
Again, a huge thanks to Bilal Mahmoud (@indietyp) for his work.
Termination
, anyhow
and eyre
We’ve made a few other improvements too:
The error-stack
documentation has been greatly improved, and we’re always open to further feedback (create an issue on GitHub).
Termination
has been implemented for Report
s: it will return the ExitCode
provided by any Context
if present, or ExitCode::FAILURE
will be returned.
Compatibility for anyhow
and eyre
has been added to convert their types into Report
.
Result<_, anyhow::Error>
or Result<_, eyre::Report>
to Result<_, Report<anyhow::Error>>
or Result<_, Report<eyre::Report>>
.anyhow::Error
and eyre::Report
are also attached as frames to maintain their history.This should make it much easier to try out error-stack
in existing codebases which utilize these libraries, as well as improving the migration process.
We care a lot about breaking changes and will always try to conform to SemVer. A few principles guide our approach:
Report::span_trace
to avoid special casing SpanTrace
and keep the API the same as for backtraces.Version 0.2 of error-stack
introduces these breaking changes and deprecations:
Provider
, so if you were on nightly and used our provider API, you need to change the imports to core::any::
.hooks
, futures
, and futures-core
Report::backtrace
(and Report::span_trace
for consistency) were deprecated because you cannot get Backtrace
from Error
anymore.
Report::downcast_ref::<Backtrace>
/ Report::downcast_ref::<SpanTrace>
.Report::request_ref::<Backtrace>
/ Report::request_ref::<SpanTrace>
.Debug
output has changed significantly. If you had a dependency on the format of the Debug
output (which we don’t recommend), it might break.IntoReport::report
. Please use IntoReport::into_report
instead.Report::set_debug_hook
please use Report::install_debug_hook
.Report::set_display_hook
.Frame::source
; please use Frame::sources
instead.Frame::source_mut
; please use Frame::sources_mut
instead.We’ve got lots of new features planned, including:
serde
to provide a hook-based interface to serialize Report
s.defmt
.Please let us know what you think!
We’ll also be using many of the new features of error-stack
v0.2 in our own products, namely HASH (learn more or view on GitHub). The new hook system will greatly simplify the creation of useful reports, and the support for related errors will allow us not to return the error the first time it occurs, but to give the user a comprehensive list of errors.
HASH, the company behind error-stack
, is 100% committed to open source and we’d love to work with you. Ask a question, file an issue, or open a PR on the error-stack repo, or any of our other projects. Also check out the Block Protocol, a new standard for building composable interfaces and interoperable types for a more linked and open semantic web.
This crate is published under the MIT License.
Get notified when new long-reads and articles go live. Follow along as we dive deep into new tech, and share our experiences. No sales stuff.