A flexible, ergonomic, and inspectable error reporting library for Rust.
Overview
This crate provides a structured way to represent and work with errors and their context. The main goal is to enable you to build rich, structured error reports that automatically capture not just what went wrong, but also the context and supporting data at each step.
Project goals
- Ergonomic: The
?operator should work with most error types, even ones not designed for this library. - Fast happy path: A
Result<(), Report>should never be larger than ausize. - Typable: Users should be able to (optionally) specify the type of the context in the root node.
- Inspectable: The objects in a Report should not be glorified strings. Inspecting and interacting with them should be easy.
- Cloneable: It should be possible to clone a
Reportwhen you need to. - Mergeable: It should be possible to merge multiple
Reports into a single one. - Customizable: It should be possible to customize what data gets collected, or how reports are formatted.
- Rich: Reports should automatically capture information (like backtraces) that might be useful in debugging.
- Beautiful: The default formatting of a Report should look pleasant (at least to the authors).
Core Concepts
At a high level, you can think of the library as a way to build a tree of error reports, where each node in the tree represents a step in the error's history.
You can think of a Report as roughly implementing this data structure:
# use Any;
;
In practice, the actual implementation differs from this somewhat. There are multiple reasons for this, including performance, ergonomics and being able to support the features we want.
However this simpler implementation does illustrate the core concepts and the ownership model well.
If you want the full details, take a look at the rootcause-internals crate.
Generics and Report Variants
To support the different, incompatible use-cases, the Report type is generic over
three parameters:
- The type of the context in the root node. The default is
dyn Any, which means that the root node can have any type of context. - The ownership and cloning behavior of the report. The default is
Mutable, which means that the root node of the report is mutable, but cannot be cloned. - The thread-safety of the objects inside the report. The default is
SendSync, which means that the report implementsSend+Sync, but also requires all objects inside it to beSend+Sync.
If you want an experience similar to anyhow, you can ignore all the type parameters.
You should simply use Report, which is the same as Report<dyn Any, Mutable, SendSync>.
If you want an experience similar to error-stack, you should use Report<SomeContextType>,
which is the same as Report<SomeContextType, Mutable, SendSync>.
Tables of Report Variants
Context Variants
| Variant | Context of root node | Context of internal nodes |
|------------------- -----------|----------------------|---------------------------|
| Report<SomeContextType,*,*> | SomeContextType | Can be anything |
| Report<dyn Any,*,*> | Can be anything | Can be anything |
Note that dyn Any though only be thought of as a marker. No actual trait object is stored anywhere. Converting
from a Report<SomeContextType> to Report<dyn Any> is a zero-cost operation.
Ownership and Cloning Variants
| Variant | Clone |
Mutation supported | Intuition |
|---|---|---|---|
Report<*,Mutable,*> |
❌ | 🟡 - Root only | Root node is allocated using ArcUnique, internal nodes using Arc. |
Report<*,Clonable,*> |
✅ | ❌ | All nodes are allocated using Arc. |
The Mutable exist to allow mutating the root node (e.g. adding attachments).
Thread-Safety Variants
| Variant | Send+Sync |
Permits insertion of !Send and !Sync objects |
Intuition |
|---|---|---|---|
Report<*,*,SendSync> |
✅ | ❌ | The objects inside the report are Send+Sync, so the report itself is also Send+Sync. |
Report<*,*,Local> |
❌ | ✅ | Since objects inside the report might not be Send+Sync, the report is !Send+!Sync. |
Converting Between Report Variants
The lists have been ordered so that it is always possible to convert to an element further
down the list using the [From] trait. This also means you can use ? when converting
downwards. There are also more specific methods (implemented using [From]), to help with
type inference and to more clearly communicate intent:
- [
Report::into_dyn_any] converts fromReport<C, *, *>toReport<dyn Any, *, *>. - [
Report::into_cloneable] converts fromReport<*, Mutable, *>toReport<*, Cloneable, *>. - [
Report::into_local] converts fromReport<*, *, SendSync>toReport<*, *, Local>.
On the other hand, it is generally harder to convert to an element further up the list. Here are some of the ways to do it:
- From
Report<dyn Any, *, *>toReport<SomeContextType, *, *>:- You can check if the type of the root node matches a specific type by using
[
Report::downcast_report]. This will return either the requested report type or the original report depending on whether the types match.
- You can check if the type of the root node matches a specific type by using
[
- From
Report<*, Clonable, *>toReport<*, Mutable, *>:- You can check if the root node only has a single owner using
[
Report::try_into_mutable]. This will check the number of references to the root node and return either the requested report variant or the original report depending on whether it is unique. - You can allocate a new root node and set the current node as a child of the
new node. The new root node will be
Mutable. One method for allocating a new root node is to call [Report::context].
- You can check if the root node only has a single owner using
[
- From
Report<*, *, *>toReport<Preformatted, Mutable, SendSync>:- You can preformat the entire the report using [
Report::preformat]. This creates an entirely new report that has the same structure and will look the same as the current one if printed, but all contexts and attachments will be replaced with aPreformattedversion.
- You can preformat the entire the report using [