Comprehensive error handling library with detailed backtrace tracking.
error2 provides enhanced error handling capabilities for Rust applications,
focusing on detailed error propagation tracking and ergonomic error conversion.
Features
- 🔍 Backtrace Tracking - Automatically capture error creation location; manually record propagation with
.attach()
- 🔗 Error Chaining - Chain errors from different libraries while preserving context
- 🎯 Derive Macro -
#[derive(Error2)] for easy error type creation
- 🔄 Type Conversion -
Result<T, E1> -> Result<T, E2>, Option<T> -> Result<T, E> with .context()
- 📦 Type Erasure -
BoxedError2 for anyhow-like ergonomics
Quick Start
Add to your Cargo.toml:
[dependencies]
error2 = "0.13.1"
Define your error types:
use std::io;
use error2::prelude::*;
#[derive(Debug, Error2)]
pub enum MyError {
#[error2(display("IO error: {source}"))]
Io {
source: io::Error,
backtrace: Backtrace,
},
#[error2(display("not found: {key}"))]
NotFound { key: String, backtrace: Backtrace },
}
Use in your functions:
# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# pub enum MyError {
# #[error2(display("IO error"))]
# Io { source: io::Error, backtrace: Backtrace },
# #[error2(display("not found: {key}"))]
# NotFound { key: String, backtrace: Backtrace },
# }
fn read_config(path: &str) -> Result<String, MyError> {
let content = std::fs::read_to_string(path).context(Io2)?;
let value = content
.lines()
.next()
.context(NotFound2 { key: "first line" })?;
Ok(value.to_string())
}
Three Error Patterns
Error2 supports three types of errors based on their field structure:
1. Root Error (New Error Origin)
Use when creating a new error (not wrapping another):
# use error2::prelude::*;
#[derive(Debug, Error2)]
pub enum AppError {
#[error2(display("invalid ID: {id}"))]
InvalidId {
id: i64,
backtrace: Backtrace, },
}
2. Std Error (Wrapping std::error::Error)
Use when wrapping standard library or third-party errors:
# use error2::prelude::*;
# use std::io;
#[derive(Debug, Error2)]
pub enum AppError {
#[error2(display("file error"))]
FileError {
source: io::Error, backtrace: Backtrace, },
}
3. Error2 Error (Chaining Error2 Types)
Use when wrapping another Error2 type (reuses backtrace):
# use error2::prelude::*;
# #[derive(Debug, Error2)]
# #[error2(display("config error"))]
# pub struct ConfigError { backtrace: Backtrace }
#[derive(Debug, Error2)]
pub enum AppError {
#[error2(display("configuration failed"))]
Config {
source: ConfigError, },
}
Core Traits
- [
Error2] - Extends std::error::Error with backtrace support
- [
Context] - Type conversion: Result<T, Source> -> Result<T, Target>, Option<T> -> Result<T, E>
- [
Attach] - Record error propagation locations
- [
RootError] - Convenience methods for creating root errors
Type Erasure
[BoxedError2] provides anyhow-like ergonomics:
use error2::prelude::*;
fn do_something() -> Result<(), BoxedError2> {
std::fs::read_to_string("file.txt").context(ViaStd)?; Ok(())
}
Location Tracking
Use .attach() to record error propagation:
# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# #[error2(display("error"))]
# struct MyError { source: io::Error, backtrace: Backtrace }
# fn inner() -> Result<(), MyError> { Ok(()) }
fn outer() -> Result<(), MyError> {
let result = inner().attach()?; Ok(result)
}
The backtrace shows multiple locations:
# use error2::prelude::*;
# use std::io;
# #[derive(Debug, Error2)]
# #[error2(display("error"))]
# struct MyError { source: io::Error, backtrace: Backtrace }
# fn inner() -> Result<(), MyError> {
# let err = io::Error::new(io::ErrorKind::NotFound, "not found");
# Err(err).context(MyError2)
# }
# fn outer() -> Result<(), MyError> { inner().attach() }
# fn main() {
use regex::Regex;
if let Err(e) = outer() {
let msg = e.backtrace().error_message();
let re = Regex::new(concat!(
r"(?s)^.+MyError: error",
r"\n at .+\.rs:\d+:\d+",
r"\n at .+\.rs:\d+:\d+",
r"\nstd::io::error::Error: not found$",
))
.unwrap();
assert!(re.is_match(msg.as_ref()));
}
# }