error-rail
use *;
Why error-rail?
Most error handling libraries format context eagerly—even on success paths where the context is never used. error-rail uses lazy evaluation, deferring string formatting until an error actually occurs.
Benchmark Results (full methodology)
| Metric | Performance |
|---|---|
| Error creation | ~296 ns |
| Error propagation overhead | ~9% overhead vs plain Rust Result (770ns vs 706ns) |
| Serialization | ~684 ns |
| Validation overhead | ~5% vs manual collection |
| Validation throughput | 5.37 M elements/sec (5000 items) |
Requirements
- Rust: 1.81.0 or later (MSRV)
- Edition: 2021
Installation
Or add to Cargo.toml:
[]
= "0.7"
30-Second Quick Start
use *;
// Add context to any Result with .ctx()
// Chain multiple contexts
New to error-rail? See the Quick Start Guide for step-by-step examples.
API Levels (New in 0.7.0)
error-rail provides a 3-level API hierarchy to match your expertise level:
Beginner API (prelude)
Start here! Everything you need for common error handling:
use *;
Exports: ComposableError, ErrorContext, ErrorPipeline, rail!, context!, group!, ResultExt, BoxedResultExt
Intermediate API (intermediate)
Advanced patterns for service developers:
use *;
// Classify errors as transient/permanent
// Custom error formatting
let formatted = err.fmt.pretty.show_code.to_string;
Exports: TransientError, ErrorFormatter, FingerprintConfig
Advanced API (advanced)
Low-level internals for library authors:
use *;
// Direct access to internal types
let vec: = /* ... */;
let builder = new;
Exports: ErrorVec, ErrorContextBuilder, LazyContext, LazyGroupContext, GroupContext
Key Features
1. Structured Error Context
Wrap any error in ComposableError and attach layered metadata.
use ;
let err = new
.with_context
.with_context
.set_code;
println!;
// Output: retry attempt 3 -> [database] at src/main.rs:7 (host=localhost:5432) -> connection failed (code: 500)
2. Error Pipeline
Chain context and transformations with a fluent builder API.
use ;
let result = new
.with_context
.with_context
.map_error // Transform error type
.finish_boxed;
if let Err = result
3. Validation Accumulation
Collect multiple errors instead of failing fast—ideal for form validation.
use Validation;
// Collect all validation results
let results: = vec!.into_iter.collect;
match results
// Output:
// Error: age must be between 0 and 150
// Error: name cannot be empty
// New in 0.7.0: validate! macro for cleaner syntax
use validate;
let age_result = validate_age;
let name_result = validate_name;
let combined = validate!;
// Returns Validation<E, (i32, &str)> with all errors accumulated
4. Efficient Lazy Context
The context! macro defers string formatting until an error actually occurs.
use ;
let payload = LargePayload ;
// format!() is not called on success path
let result = new
.with_context
.finish_boxed;
Performance: In benchmarks, lazy context adds minimal overhead on success. Eager formatting (4,277ns) is significantly slower than lazy context (613ns) on success paths.
When to Use .ctx() vs context!()
| Method | Use Case | Example |
|---|---|---|
.ctx("static") |
Simple static messages | .ctx("loading file") |
context!() |
Formatted messages with variables | context!("user_id: {}", id) |
.ctx_with() |
Complex closure logic | .ctx_with(|| expensive_calculation()) |
Decision Guide:
- Use
.ctx("static")for simple strings - no allocation overhead - Use
.ctx(context!())when formatting variables - 7x faster than eager formatting on success paths - Both methods add the same context type, only evaluation timing differs
// ✅ Simple static context
result.ctx
// ✅ Lazy formatted context (recommended for variables)
result.ctx
// ❌ Eager formatting (slow on success paths)
result.ctx
5. Convenient Macros
| Macro | Purpose | Example |
|---|---|---|
context! |
Lazy formatted message | context!("user_id: {}", id) |
group! |
Structured context with multiple fields | group!(tag("db"), location(file!(), line!())) |
rail! |
Quick pipeline wrapper | rail!(fallible_fn()) |
use ;
// rail! is shorthand for ErrorPipeline::new(...).finish_boxed()
let result = rail!;
6. Transient Error Classification
Classify errors as transient (retryable) or permanent for integration with retry libraries.
use ;
use Duration;
// Use with ErrorPipeline
Note: error-rail does not implement retry logic itself. Use external libraries like
backoff,retry, ortokio-retry.
7. Error Fingerprinting
Generate unique fingerprints for error deduplication in monitoring systems.
use ;
let err = new
.with_context
.with_context
.set_code;
// Generate fingerprint for Sentry/logging deduplication
println!;
// Example output: "a1b2c3d4e5f67890"
// Customize what's included in fingerprint
let fp = err.fingerprint_config
.include_message // Ignore variable message content
.include_metadata // Include metadata
.compute_hex;
8. Type Aliases for Ergonomics
use *;
// Recommended: BoxedResult for public API (8 bytes stack)
// Alternative: ComposableResult for internal use (larger stack)
use ComposableResult;
Type Aliases Comparison
| Type Alias | Definition | Stack Size | Use When |
|---|---|---|---|
BoxedResult<T, E> |
Result<T, Box<ComposableError<E>>> |
8 bytes | Recommended for public APIs |
BoxedComposableResult<T, E> |
Same as BoxedResult |
8 bytes | Legacy alias (identical) |
ComposableResult<T, E> |
Result<T, ComposableError<E>> |
48+ bytes | Internal functions only |
Note:
BoxedResultandBoxedComposableResultare identical. UseBoxedResultfor brevity.
When to Use What?
Quick Reference
| Scenario | Recommended Type | Example |
|---|---|---|
| Simple error wrapping | ComposableError<E> |
Internal error handling |
| Function return type | BoxedResult<T, E> |
Public API boundaries |
| Adding context to Result | ErrorPipeline |
Wrapping I/O operations |
| Form/input validation | Validation<E, T> |
Collecting all field errors |
| Error chaining | ErrorPipeline + finish_boxed() |
Multi-step operations |
| Retry logic | TransientError trait |
Network timeouts, rate limiting |
| Error deduplication | fingerprint() / fingerprint_hex() |
Sentry grouping, log dedup |
Validation vs Result
| Feature | Result<T, E> |
Validation<E, T> |
|---|---|---|
| Short-circuit | Yes (stops at first error) | ❌ No (collects all) |
| Use case | Sequential operations | Parallel validation |
| Error count | Single | Multiple |
| Iterator support | ? operator |
.collect() |
Common Pitfalls
1. Forgetting to Box for Return Types
// ❌ Large stack size (48+ bytes per Result)
// ✅ Reduced stack size (8 bytes pointer)
2. Excessive Context Depth
// ❌ Adding context at every layer (O(n) performance)
db_call
.with_context
.and_then
.and_then
// ... 20 more layers
// ✅ Add context at boundaries only
let result = db_call
.and_then
.and_then;
new
.with_context
.finish_boxed
3. Eager vs Lazy Context
// ❌ Eager: format! runs even on success
.with_context
// ✅ Lazy: format! only runs on error
.with_context
Module Reference
| Module | Description |
|---|---|
prelude |
Start here! Common imports: ResultExt, BoxedResult, macros |
context |
Context attachment: with_context, accumulate_context, format_error_chain |
convert |
Conversions between Result, Validation, and ComposableError |
macros |
context!, group!, rail!, impl_error_context! |
traits |
ResultExt, BoxedResultExt, IntoErrorContext, ErrorOps, WithError, TransientError |
types |
ComposableError, ErrorContext, ErrorPipeline, LazyContext, FingerprintConfig |
validation |
Validation<E, A> type with collectors and iterators |
Feature Flags
| Feature | Description | Default |
|---|---|---|
std |
Standard library support (enables backtrace! macro) |
❌ No |
serde |
Serialization/deserialization support | ❌ No |
full |
Enable all features (std + serde) |
❌ No |
Usage Examples
# Default (no_std compatible, requires alloc)
[]
= "0.7"
# With std library support (e.g., for backtraces)
[]
= { = "0.7", = ["std"] }
# With serialization support
[]
= { = "0.7", = ["serde"] }
# All features enabled
[]
= { = "0.7", = ["full"] }
no_std Support
error-rail is no_std compatible by default. It requires only the alloc crate.
# Minimal no_std usage
[]
= { = "0.7", = false }
Note: Some features like
backtrace!require thestdfeature.
Examples
Integration Guides
- Quick Start Guide - Step-by-step tutorial
- Error Handling Patterns - Real-world usage patterns and best practices
Glossary
| Term | Definition |
|---|---|
| Context | Additional information attached to an error (location, tags, messages) |
| Metadata | Key-value pairs within context (subset of context) |
| Pipeline | Builder pattern for chaining error operations (ErrorPipeline) |
| Boxed Error | Heap-allocated error via Box<ComposableError<E>> for reduced stack size |
| Lazy Evaluation | Deferred computation until actually needed (e.g., context! macro) |
| Validation | Accumulating type that collects all errors instead of short-circuiting |
| Transient Error | Temporary failure that may succeed on retry (e.g., timeout, rate limit) |
| Fingerprint | Unique hash of error components for deduplication and grouping |
License
Licensed under the Apache License 2.0. See LICENSE for details.
For third-party attributions, see NOTICE.