How does anyhow::Chain::new enable iterating through the chain of causes in an error?
anyhow::Chain provides iterator access to the causal chain of errors, allowing you to walk through each error in a chain from the outermost error to the root cause. The chain() method on anyhow::Error returns a Chain iterator that yields each underlying error source in sequence. This is essential for debugging and logging where you need to see the complete error history rather than just the final error message.
Understanding Error Chains in anyhow
use anyhow::{Error, Context, Result};
fn deep_operation() Result<()> {
Err(anyhow::anyhow!("database connection failed"))
}
fn middle_operation() Result<()> {
deep_operation().context("failed to process request")
}
fn outer_operation() Result<()> {
middle_operation().context("request handler failed")
}
fn main() {
match outer_operation() {
Ok(_) => println!("Success"),
Err(e) => {
println!("Final error: {}", e);
println!("Error chain:");
for (i, cause) in e.chain().enumerate() {
println!(" {}: {}", i, cause);
}
}
}
// Output:
// Final error: request handler failed
// Error chain:
// 0: request handler failed
// 1: failed to process request
// 2: database connection failed
}The chain shows all errors from outermost to innermost (root cause).
The Chain Iterator
use anyhow::{Context, Result};
fn main() -> Result<()> {
// Create a chain of errors
let error = Err(anyhow::anyhow!("root cause"))
.context("level 1")
.context("level 2")
.context("level 3")
.unwrap_err();
// Chain implements Iterator
let chain = error.chain();
println!("Iterating with for loop:");
for cause in chain {
println!(" - {}", cause);
}
// Can also collect into a Vec
let causes: Vec<_> = error.chain().collect();
println!("\nCollected {} causes", causes.len());
Ok(())
}Chain implements Iterator<Item = &dyn std::error::Error>.
Chain::new vs chain() Method
use anyhow::{Error, Chain};
fn main() {
// The public API is error.chain()
// Chain::new is internal - you typically don't call it directly
let error = anyhow::anyhow!("error").context("context");
let chain = error.chain();
// Chain::new is used internally by the chain() method
// It takes a reference to the Error and creates the iterator
// You can still examine the Chain type:
println!("Chain type: {}", std::any::type_name::<Chain<'_>>());
// Chain implements:
// - Iterator<Item = &dyn std::error::Error>
// - ExactSizeIterator (if the underlying chain supports it)
// - Debug
let causes: Vec<_> = chain.collect();
println!("Number of causes: {}", causes.len());
}Chain::new is internal; use error.chain() to get the iterator.
Walking to the Root Cause
use anyhow::{Context, Result};
fn find_root_cause(error: &anyhow::Error) -> &(dyn std::error::Error + 'static) {
error.chain().last().unwrap_or(error.as_ref())
}
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("root cause error"))
.context("middle error")
.context("outer error")
.unwrap_err();
println!("Full error: {}", error);
println!("Root cause: {}", find_root_cause(&error));
// Or iterate to find specific error types
let error = Err(anyhow::anyhow!("connection timeout"))
.context("query failed")
.context("database error")
.context("request failed")
.unwrap_err();
// Find first error containing "connection"
let connection_error = error.chain()
.find(|e| e.to_string().contains("connection"));
if let Some(e) = connection_error {
println!("Found connection error: {}", e);
}
Ok(())
}chain().last() gives the root cause; chain().find() locates specific errors.
Formatting Error Chains for Logging
use anyhow::{Context, Result};
fn format_error_chain(error: &anyhow::Error) -> String {
let mut parts = Vec::new();
// Include the main error message
parts.push(format!("Error: {}", error));
// Add each cause on its own line with indentation
for (i, cause) in error.chain().enumerate() {
parts.push(format!(" caused by: {}", cause));
}
parts.join("\n")
}
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("file not found: config.toml"))
.context("failed to load configuration")
.context("application startup failed")
.unwrap_err();
println!("{}", format_error_chain(&error));
// Output:
// Error: application startup failed
// caused by: failed to load configuration
// caused by: file not found: config.toml
Ok(())
}Formatting chains helps with debugging and error reports.
Comparing with std::error::Error::source
use anyhow::{Context, Result};
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("root"))
.context("level 1")
.context("level 2")
.unwrap_err();
// Method 1: anyhow chain()
println!("Using anyhow::Chain:");
for (i, cause) in error.chain().enumerate() {
println!(" {}: {}", i, cause);
}
// Method 2: std::error::Error::source (manual iteration)
println!("\nUsing std::error::Error::source:");
let mut current: Option<&dyn std::error::Error> = Some(error.as_ref());
let mut i = 0;
while let Some(err) = current {
println!(" {}: {}", i, err);
current = err.source();
i += 1;
}
// Both produce the same output
// chain() is more ergonomic and idiomatic
Ok(())
}chain() wraps the manual source() iteration into a clean iterator.
Error Chain Length Analysis
use anyhow::{Context, Result};
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("root"))
.context("level 1")
.context("level 2")
.context("level 3")
.context("level 4")
.context("level 5")
.unwrap_err();
// Chain provides iterator methods
let chain = error.chain();
// Check chain length
let depth = chain.clone().count();
println!("Error chain depth: {}", depth);
// Find specific depth conditions
if error.chain().count() > 3 {
println!("Warning: Deep error chain may indicate over-wrapping");
}
// Use iterator adapters
let causes: Vec<String> = error.chain()
.map(|e| e.to_string())
.collect();
println!("All causes: {:?}", causes);
Ok(())
}Iterator methods allow chain analysis and filtering.
Real-World Example: Error Handling Middleware
use anyhow::{Context, Result};
#[derive(Debug)]
struct ApiError {
code: u16,
message: String,
causes: Vec<String>,
}
impl ApiError {
fn from_anyhow(error: anyhow::Error) -> Self {
let causes: Vec<String> = error.chain()
.map(|e| e.to_string())
.collect();
// Classify based on root cause
let root_cause = causes.last().map(|s| s.as_str()).unwrap_or("");
let code = if root_cause.contains("connection") {
503 // Service Unavailable
} else if root_cause.contains("not found") {
404 // Not Found
} else if root_cause.contains("permission") {
403 // Forbidden
} else {
500 // Internal Server Error
};
ApiError {
code,
message: error.to_string(),
causes,
}
}
}
fn database_query() Result<()> {
Err(anyhow::anyhow!("connection timeout"))
}
fn handle_request() Result<()> {
database_query().context("failed to process user request")
}
fn main() {
match handle_request() {
Ok(_) => println!("Success"),
Err(e) => {
let api_error = ApiError::from_anyhow(e);
println!("HTTP {}: {}", api_error.code, api_error.message);
for (i, cause) in api_error.causes.iter().enumerate() {
println!(" Cause {}: {}", i, cause);
}
}
}
}Error chains enable detailed error classification and reporting.
Debug vs Display Output
use anyhow::{Context, Result};
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("io error: permission denied"))
.context("failed to read file")
.context("config load failed")
.unwrap_err();
// Display shows only the outermost error
println!("Display: {}", error);
// Debug shows the full chain with backtrace (if enabled)
println!("\nDebug: {:?}", error);
// Custom formatting with chain
println!("\nCustom chain:");
for cause in error.chain() {
println!(" {}", cause);
}
// Alternative: {:#?} for pretty debug
println!("\nPretty Debug: {:#?}", error);
Ok(())
}Different format traits show different levels of detail.
Downcasting Errors in the Chain
use anyhow::{Context, Result};
use std::error::Error;
#[derive(Debug)]
struct DatabaseError {
code: i32,
message: String,
}
impl std::fmt::Display for DatabaseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "DatabaseError({}): {}", self.code, self.message)
}
}
impl std::error::Error for DatabaseError {}
fn main() -> Result<()> {
let db_error = DatabaseError {
code: 1205,
message: "Lock wait timeout".to_string(),
};
let error = Err(anyhow::Error::new(db_error))
.context("transaction failed")
.context("request handler failed")
.unwrap_err();
// Search chain for specific error type
// Note: downcasting requires the error type to be stored
// anyhow::Error stores inner errors as dyn Error
println!("Searching for DatabaseError in chain:");
for cause in error.chain() {
println!(" Checking: {}", cause);
// You'd need to use downcasting on the original error
// The chain() iterator yields &dyn Error
}
// Use root_cause() and downcast on the original error
if let Some(db_err) = error.root_cause().downcast_ref::<DatabaseError>() {
println!("\nFound DatabaseError: code={}", db_err.code);
}
Ok(())
}Downcasting allows type checking on error causes.
Chain with Backtraces
use anyhow::{Context, Result, Backtrace};
fn main() -> Result<()> {
// Enable RUST_BACKTRACE=1 or RUST_BACKTRACE=full for backtraces
let error = Err(anyhow::anyhow!("something failed"))
.context("context added here")
.context("outer context")
.unwrap_err();
// The backtrace is captured at the point of error creation
println!("Error: {}", error);
// Chain doesn't include backtraces directly
// Backtraces are part of the Error itself
for (i, cause) in error.chain().enumerate() {
println!("Cause {}: {}", i, cause);
}
// Backtrace is available on the error if RUST_BACKTRACE is set
if std::env::var("RUST_BACKTRACE").is_ok() {
// Backtrace would be included in {:?} formatting
println!("\nWith backtrace: {:?}", error);
}
Ok(())
}Backtraces are captured with anyhow::Error and shown in debug output.
Practical Error Handling Pattern
use anyhow::{Context, Result};
fn log_error(error: &anyhow::Error) {
eprintln!("ERROR: {}", error);
// Log full chain for debugging
let chain: Vec<_> = error.chain().collect();
if chain.len() > 1 {
eprintln!("Caused by:");
for cause in chain {
eprintln!(" - {}", cause);
}
}
}
fn classify_error(error: &anyhow::Error) -> &'static str {
// Check for specific error patterns in the chain
let error_string = error.to_string();
for cause in error.chain() {
let cause_str = cause.to_string();
if cause_str.contains("timeout") || cause_str.contains("deadline") {
return "timeout";
}
if cause_str.contains("not found") || cause_str.contains("missing") {
return "not_found";
}
if cause_str.contains("permission") || cause_str.contains("forbidden") {
return "forbidden";
}
}
"unknown"
}
fn main() -> Result<()> {
let error = Err(anyhow::anyhow!("connection timeout"))
.context("database query failed")
.context("user request failed")
.unwrap_err();
log_error(&error);
println!("Classification: {}", classify_error(&error));
Ok(())
}Error chains enable robust error handling and classification.
Synthesis
Quick reference:
use anyhow::{Context, Result};
fn main() -> Result<()> {
// Create an error chain
let error = Err(anyhow::anyhow!("root cause"))
.context("level 1")
.context("level 2")
.context("level 3")
.unwrap_err();
// Iterate over the chain
for (i, cause) in error.chain().enumerate() {
println!("{}: {}", i, cause);
}
// Output:
// 0: level 3
// 1: level 2
// 2: level 1
// 3: root cause
// Get the root cause (last in chain)
let root = error.root_cause();
println!("Root cause: {}", root);
// Count depth
let depth = error.chain().count();
println!("Chain depth: {}", depth);
// Find specific error in chain
let found = error.chain()
.find(|e| e.to_string().contains("level 1"));
// Collect causes into a Vec
let causes: Vec<String> = error.chain()
.map(|e| e.to_string())
.collect();
// Chain::new is internal - use error.chain() instead
// Chain implements:
// - Iterator<Item = &dyn std::error::Error>
// - ExactSizeIterator (when supported)
// - Clone
// - Debug
Ok(())
}
// Key points:
// - chain() iterates from outermost to innermost (root cause)
// - Chain is an iterator, so you can use all iterator methods
// - Use root_cause() to get the deepest error directly
// - Use chain().last() as an alternative to root_cause()
// - Each context() adds a new layer to the chain
// - The chain is empty if error has no underlying causeKey insight: anyhow::Chain bridges the gap between anyhow::Error and the standard std::error::Error::source() chain. While source() requires manual iteration through error causes, Chain provides a clean iterator interface that works with all of Rust's iterator adapters. Use chain() to walk through the complete error history for logging, debugging, or error classification. The iterator yields errors from outermost to innermost, making it easy to find the root cause with .last() or search for specific error patterns with .find(). This is essential for production error handling where the full context matters for debugging.
