rootcause
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.
It allows printing pretty, tree-structured reports like this one:
● Unable to fetch document http://example.com
├ examples/readme-example.rs:45:21
│
├─ ● HTTP error: 400 Bad Request: Could not parse JSON payload
│ ├ examples/readme-example.rs:32:9
│ ╰ Attempt #1
│
╰─ ● HTTP error: 500 Internal server error
├ examples/readme-example.rs:32:9
╰ Attempt #2
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
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:
;
The actual implementation follows the same basic structure, but has a few more complications. There are multiple reasons for this, including performance, ergonomics and being able to support the features we want.
Quick Start
Add this to your Cargo.toml:
[]
= "0.3.0"
Basic Usage
use *;
With Custom Context Types
use *;
// You might also want to implement `std::fmt::Display`,
// as otherwise this example will print out:
// ● Context of type `example::MyError`
// ╰ src/main.rs:19:9
Report Variants
The Report type is generic over three parameters to support different use cases:
- Context Type: The type of the context in the root node (default:
dyn Any) - Ownership: The ownership and cloning behavior (default:
Mutable) - Thread Safety: Whether the report is
Send + Sync(default:SendSync)
Context Variants
| Variant | Root Context | Internal Contexts | Use Case |
|---|---|---|---|
Report<SomeContextType> |
SomeContextType |
Can be anything | Similar to error-stack |
Report<dyn Any> (default) |
Can be anything | Can be anything | Similar to anyhow |
Ownership Variants
| Variant | Clone |
Mutation | Description |
|---|---|---|---|
Report<*, Mutable> (default) |
❌ | Root only | Root allocated with UniqueArc, internals with Arc |
Report<*, Cloneable> |
✅ | ❌ | All nodes allocated with Arc |
Thread Safety Variants
| Variant | Send + Sync |
Allows !Send/!Sync objects |
Description |
|---|---|---|---|
Report<*, *, SendSync> (default) |
✅ | ❌ | All objects must be Send + Sync |
Report<*, *, Local> |
❌ | ✅ | Allows non-thread-safe objects |
Converting Between Variants
You can convert between report variants using the From trait or specific methods:
use *;
// Convert to more general variants (always possible)
let report: = report!;
let general: Report = report.into_dyn_any;
let cloneable: = general.into_cloneable;
// Convert to more specific variants (conditional)
let specific: = general.downcast_report;
let mutable: = cloneable.try_into_mutable;
Features
std(default): Enable standard library supportbacktrace: Automatic backtrace capture on error creation
Comparison with Other Libraries
vs. anyhow
- ✅ More structured and inspectable errors
- ✅ Better support for custom context types
- ✅ Richer attachment system
- ❌ Slightly more complex API
vs. error-stack
- ✅ More flexible ownership models
- ✅ Better ergonomics with the
?operator - ✅ More customizable formatting
- ❌ Different API surface
vs. thiserror
- ✅ Runtime error composition vs. compile-time
- ✅ Automatic context capture
- ✅ Rich attachment system
- ❌ More overhead for simple cases
Architecture
The library consists of two main crates:
rootcause: The main user-facing APIrootcause-internals: Low-level implementation details and most of the unsafe code
Minimum Supported Rust Version (MSRV)
Our current Minimum Supported Rust Version is 1.89.0. When adding features, we will follow these guidelines:
- Our goal is to support at least five minor Rust versions. This gives you a 6 month window to upgrade your compiler.
- Any change to the MSRV will be accompanied with a minor version bump.
Acknowledgements
This library was inspired by and draws ideas from several existing error handling libraries in the Rust ecosystem, including anyhow, thiserror, and error-stack.