erract - Structured Error Handling for Rust
erract provides production-ready error handling that solves real problems:
- Actionable errors: Categorized by what callers can do (retry, fail, etc.)
- Explicit retry semantics: No guessing from error messages
- Rich context: Automatic location capture with zero overhead
- Type safety: Enforce context at module boundaries
Philosophy
Two audiences, two needs:
- Machines: Need flat structures, clear error kinds, predictable codes
- Humans: Need rich context, call paths, business-level information
This library combines the best ideas from:
- Apache OpenDAL's error design
- The exn crate
- Years of production error handling experience
Quick Start
use *;
Features
- Zero runtime overhead: Uses
#[track_caller]instead of expensive backtraces - Domain-specific errors: HTTP, Database, and Storage error kinds
- Error trees: Support for multiple concurrent failures
- Type safety: Compiler enforces context at module boundaries
- Zero-copy static strings: Use
Cow<'static, str>for static messages - Optimized serialization: Fast JSON and machine-readable output
API Highlights
Static Constructors (Zero Allocation)
// Common errors with pre-defined static messages
let err = not_found; // "not found"
let err = timeout; // "operation timed out"
let err = permission_denied; // "permission denied"
let err = validation_failed; // "validation failed"
let err = unexpected; // "unexpected error"
Context Methods
// For string context (zero-copy for static strings)
error.with_context
// For non-string values (uses ToString)
error.with_context_value
Serialization
let error = not_found
.with_context
.with_operation;
// Human-readable
println!; // "not found (operation: fetch) [resource: user]"
// Machine-readable
error.to_machine_string // "kind=not_found;status=permanent;message=not found;..."
// JSON (optimized, ~220ns)
error.to_json // {"kind":"not_found","status":"permanent",...}
Comparison with anyhow
erract and anyhow serve different use cases:
| Aspect | erract | anyhow |
|---|---|---|
| Design Goal | Production services with structured errors | Quick prototyping, application code |
| Retry Semantics | Explicit (is_retryable(), is_permanent()) |
None (manual parsing) |
| Error Kind | Structured enum (ErrorKind) |
Dynamic (downcast required) |
| Context Model | Key-value pairs | String messages |
| Machine Output | Built-in JSON, machine strings | Manual formatting |
| Location Tracking | #[track_caller] (zero cost) |
Backtrace (optional overhead) |
Performance Benchmarks
Run cargo bench to reproduce. Results on Linux x86_64:
| Benchmark | erract | anyhow | Winner |
|---|---|---|---|
| Basic error creation | 22 ns | 30 ns | erract (+27%) |
| Formatted error | 81 ns | 106 ns | erract (+24%) |
| Single context | 24 ns | 58 ns | erract (+59%) |
| Triple context | 190 ns | 115 ns | anyhow (+39%) |
| to_string | 268 ns | 52 ns | anyhow |
| to_json | 221 ns | N/A | erract only |
is_retryable() check |
<1 ns | N/A | erract only |
Key Insights:
- erract excels at error creation, single context, and structured queries
- anyhow excels at deep error propagation and display formatting
- Memory: erract::Error is 112 bytes (structured), anyhow::Error is 8 bytes (pointer)
- Trade-off: erract uses more stack space for richer structured data
When to Choose
Choose erract when:
- Building production services with retry logic
- Need machine-parseable errors for alerting/metrics
- Want explicit error semantics at compile time
- Multiple teams consume your error data
Choose anyhow when:
- Building CLI tools or quick prototypes
- Error handling is human-only (logs, not machines)
- Minimal boilerplate is priority
- Don't need structured error classification
See benches/README.md for detailed benchmark methodology.
License
This project is licensed under either of
- Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)
at your option.