rootcause
A flexible, ergonomic, and inspectable error reporting library for Rust.
Overview
rootcause helps you build rich, structured error reports that capture not just what went wrong, but the full context and history.
Here's a simple example (from examples/basic.rs) showing how errors build up context as they propagate through your call stack:
use *;
When startup() fails, you get a chain showing the full story:
● Application startup failed
├ examples/basic.rs:76:10
├ Environment: production
│
● Failed to load application configuration
├ examples/basic.rs:47:35
├ Config path: /nonexistent/config.toml
├ Expected format: TOML
│
● No such file or directory (os error 2)
╰ examples/basic.rs:34:19
Each layer adds context and debugging information, building a trail from the high-level operation down to the root cause.
Core Concepts
At a high level, rootcause helps you build a tree of error reports. Each node in the tree represents a step in the error's history - you start with a root error, then add context and attachments as it propagates up through your code.
Most error reports are linear chains (just like anyhow), but the tree structure lets you collect multiple related errors when needed.
Project Goals
- Ergonomic: The
?operator should work with most error types, even ones not designed for this library - Multi-failure tracking: When operations fail multiple times (retry attempts, batch processing, parallel execution), all failures should be captured and preserved in a single report
- Inspectable: The objects in a Report should not be glorified strings. Inspecting and interacting with them should be easy
- Optionally typed: Users should be able to (optionally) specify the type of the context in the root node
- Beautiful: The default formatting should look pleasant—and if it doesn't match your style, the hook system lets you customize it
- Cloneable: It should be possible to clone a
Reportwhen you need to - Self-documenting: Reports should automatically capture information (like backtraces and locations) that might be useful in debugging
- Customizable: It should be possible to customize what data gets collected, or how reports are formatted
- Lightweight:
Reporthas a pointer-sized representation, keepingResult<T, Report>small and fast
Why rootcause?
Collecting Multiple Errors
When operations fail multiple times (retries, batch processing, parallel tasks), rootcause lets you gather all the failures into a single tree structure with readable output (from examples/retry_with_collection.rs):
use ;
The output from the above function will be a tree with data associated to each node:
● Unable to fetch document http://example.com
├ examples/retry_with_collection.rs:59:16
│
├─ ● HTTP error: 500 Internal server error
│ ├ examples/retry_with_collection.rs:42:9
│ ╰ Attempt #1
│
╰─ ● HTTP error: 404 Not found
├ examples/retry_with_collection.rs:42:9
╰ Attempt #2
For more tree examples, see retry_with_collection.rs and batch_processing.rs.
Inspecting Error Trees
Errors aren't just formatted strings—they're structured objects you can traverse and analyze programmatically. This enables analytics, custom error handling, and automated debugging (from examples/inspecting_errors.rs):
use *;
// Analyze retry failures to extract structured data
This lets you extract retry statistics, categorize errors, or build custom monitoring—not just display them. See inspecting_errors.rs for complete examples.
Supporting Advanced Use Cases
When you need to use the same error in multiple places—like sending to a logging backend and displaying to a user, potentially on different threads—you can make errors cloneable:
use *;
// Make the error cloneable so we can use it multiple times
let error = fetch_data.unwrap_err.into_cloneable;
// Send to background logging service
let log_error = error.clone;
spawn;
// Also display to user
display_error;
For the niche case where you're working with !Send errors from other libraries or need to attach thread-local data:
// Include thread-local data in error reports
let report: = report!
.attach;
Most code uses the defaults, but these type parameters are available when you need them. See Report Type Parameters for details.
Type-Safe Error Handling
Libraries often need to preserve specific error types so callers can handle errors programmatically. Use Report<YourError> to enable pattern matching without runtime type checks (simplified from examples/typed_reports.rs):
use *;
// Library function returns typed error
// Caller can pattern match to handle errors intelligently
See typed_reports.rs for a complete example with retry logic.
Quick Start
Add this to your Cargo.toml:
[]
= "0.8.0"
Use Report as your error type:
use *;
That's it! The ? operator automatically converts any error type to Report.
Ready to learn more? See examples/basic.rs for a hands-on tutorial covering .context(), .attach(), and composing error chains.
Next Steps
- New to rootcause? See
examples/basic.rsfor a hands-on introduction - More examples: Browse the
examples/directory for common patterns - Full API documentation: docs.rs/rootcause
Features
std(default): Enable standard library supportbacktrace: Automatic backtrace capture on error creation
Coming from other libraries?
| Feature | anyhow | error-stack | rootcause |
|---|---|---|---|
| Error structure | Linear chain | Opaque attachment model | Explicit tree |
| Type safety | No | Required | Optional |
| Adding context | .context() |
.change_context() |
.context() |
| Structured attachments | No | Yes (.attach_printable()) |
Yes (.attach()) |
| Tree navigation | Linear (.chain()) |
Limited iterators | Full tree access |
| Cloneable errors | No | No | Yes (Report<_, Cloneable>) |
| Thread-local errors | No | No | Yes (Report<_, _, Local>) |
| Location tracking | Single backtrace | Per-attachment | Per-attachment |
| Customization hooks | No | Formatting only | Creation + formatting |
See the retry example in "Why rootcause?" for how the tree structure enables collecting multiple related errors with ReportCollection.
Advanced Features
Once you're comfortable with the basics, rootcause offers powerful features for complex scenarios. See the examples directory for patterns including:
retry_with_collection.rs- Collecting multiple retry attemptsbatch_processing.rs- Gathering errors from parallel operationsinspecting_errors.rs- Programmatic tree traversal and data extraction for analyticscustom_handler.rs- Customizing error formatting and data collectionformatting_hooks.rs- Advanced formatting customization
Architecture
The library consists of two main crates:
rootcause: The main user-facing API. Contains safe abstractions and some unsafe code for type parameter handling.rootcause-internals: Low-level implementation details. Contains the majority of unsafe code, isolated from user-facing APIs.
This separation ensures that most unsafe operations are contained in a single, auditable crate. The public API in rootcause uses these primitives to provide safe, ergonomic error handling.
Stability and Roadmap
Current status: Pre-1.0 (v0.8.0)
rootcause follows semantic versioning. As a 0.x library, breaking changes may occur in minor version bumps (0.x → 0.x+1). We're actively refining the API based on real-world usage and focused on reaching 1.0.
When to adopt:
- ✅ Now: If you value the inspection capabilities and can tolerate occasional breaking changes during 0.x
- ⏳ Wait for 1.0: If you need long-term API stability guarantees
We're committed to reaching 1.0, but we want to get the API right first.
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.