How does anyhow::Chain::new enable iterating through the chain of causes in an error?
anyhow::Chain provides an iterator over the causal chain of errors, allowing you to traverse from the outermost error through each underlying cause until you reach the root error. The chain() method on anyhow::Error returns a Chain iterator that yields &dyn std::error::Error references for each error in the cause chain. This is essential for debugging and error reporting, as it lets you present the full context of why an operation failed, not just the immediate error message.
Basic Error Chain Iteration
use anyhow::{Context, Result};
fn inner_operation() -> Result<()> {
std::fs::read_to_string("nonexistent.txt")
.context("Failed to read file")?;
Ok(())
}
fn middle_operation() -> Result<()> {
inner_operation()
.context("Processing failed")?;
Ok(())
}
fn outer_operation() -> Result<()> {
middle_operation()
.context("Operation aborted")?;
Ok(())
}
fn main() {
match outer_operation() {
Ok(()) => println!("Success"),
Err(err) => {
println!("Error: {}", err);
println!("\nCause chain:");
for cause in err.chain() {
println!(" - {}", cause);
}
}
}
}The chain shows each error from outermost to innermost.
Understanding Chain Structure
use anyhow::{anyhow, Context, Result};
use std::error::Error;
fn main() {
// Create a chain of errors
let err = anyhow!("Root error")
.context("First context")
.context("Second context")
.context("Third context");
// Iterate through the chain
println!("Full chain:");
for (i, cause) in err.chain().enumerate() {
println!(" {}: {}", i, cause);
}
// The chain iterates from outermost to innermost:
// 0: Third context
// 1: Second context
// 2: First context
// 3: Root error
}Each context() call wraps the previous error, creating a chain.
Accessing Individual Causes
use anyhow::{anyhow, Context, Result};
fn main() -> Result<()> {
let err = anyhow!("Database connection failed")
.context("Query could not execute")
.context("Transaction aborted");
// Get the outermost error message
println!("Outer: {}", err);
// Iterate through all causes
let causes: Vec<_> = err.chain().collect();
println!("Number of causes: {}", causes.len());
// First cause is the outermost
if let Some(first) = causes.first() {
println!("First (outermost): {}", first);
}
// Last cause is the root
if let Some(root) = causes.last() {
println!("Root cause: {}", root);
}
// Skip the outermost to get to inner causes
println!("\nInner causes:");
for cause in err.chain().skip(1) {
println!(" {}", cause);
}
Ok(())
}The chain preserves order from outermost to innermost.
Real-World Error Chain
use anyhow::{Context, Result};
use std::fs;
fn read_config() -> Result<String> {
fs::read_to_string("config.toml")
.context("Failed to read config file")?
.parse::<String>()
.context("Failed to parse config content")
}
fn connect_database(config: &str) -> Result<()> {
// Simulate database connection
Err(anyhow::anyhow!("Connection refused"))
.context(format!("Using config: {}", config))
}
fn run_application() -> Result<()> {
let config = read_config()
.context("Application startup failed")?;
connect_database(&config)
.context("Database initialization failed")?;
Ok(())
}
fn main() {
if let Err(err) = run_application() {
println!("Application error: {}", err);
println!("\nFull error chain:");
for (i, cause) in err.chain().enumerate() {
println!(" [{}] {}", i, cause);
}
// Output might look like:
// [0] Database initialization failed
// [1] Using config: ...
// [2] Connection refused
}
}Real applications chain errors through multiple layers.
Working with Chain Directly
use anyhow::{anyhow, Context, Result};
use std::error::Error;
fn main() -> Result<()> {
let err = anyhow!("Base error")
.context("Wrapped once")
.context("Wrapped twice");
// chain() returns Chain, which implements Iterator
let chain = err.chain();
// Chain is iterable
for cause in chain {
println!("Cause: {}", cause);
}
// Chain also has other useful methods
println!("\nUsing Chain methods:");
// Count causes
let err = anyhow!("error").context("a").context("b").context("c");
let count = err.chain().count();
println!("Cause count: {}", count);
// Last cause (root error)
if let Some(root) = err.chain().last() {
println!("Root cause: {}", root);
}
// Find specific error type
let err = anyhow!("error")
.context("context 1")
.context("context 2");
let found = err.chain().find(|c| c.to_string().contains("context 1"));
println!("Found: {:?}", found.map(|c| c.to_string()));
Ok(())
}Chain implements standard iterator methods.
Error Type Inspection
use anyhow::{anyhow, Context, Result};
use std::error::Error;
// Custom error types
#[derive(Debug)]
struct NetworkError {
message: String,
}
impl std::fmt::Display for NetworkError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Network error: {}", self.message)
}
}
impl Error for NetworkError {}
fn simulate_network_error() -> Result<()> {
Err(NetworkError {
message: "Connection timeout".to_string(),
}.into())
}
fn main() -> Result<()> {
let err = simulate_network_error()
.context("Service call failed")
.context("Application error");
// Look for specific error type in chain
for cause in err.chain() {
// Try to downcast to specific type
if let Some(network_err) = cause.downcast_ref::<NetworkError>() {
println!("Found network error: {}", network_err.message);
} else {
println!("Other cause: {}", cause);
}
}
// Alternative: use root_cause() for the innermost error
let root = err.root_cause();
println!("\nRoot cause: {}", root);
// root_cause() is equivalent to chain().last()
let chain_root = err.chain().last().unwrap();
println!("Chain root: {}", chain_root);
Ok(())
}You can inspect error types within the chain.
Formatting Error Chains
use anyhow::{anyhow, Context, Result};
fn format_error_chain(err: &anyhow::Error) -> String {
let mut output = format!("Error: {}\n", err);
output.push_str("Causes:\n");
for (i, cause) in err.chain().enumerate() {
output.push_str(&format!(" {}. {}\n", i + 1, cause));
}
output
}
fn main() {
let err = anyhow!("File not found")
.context("Could not load resource")
.context("Startup failed");
println!("{}", format_error_chain(&err));
// Alternative: Pretty printing with indentation
fn pretty_print_chain(err: &anyhow::Error) {
println!("{}", err);
for (depth, cause) in err.chain().enumerate() {
let indent = " ".repeat(depth + 1);
println!("{}→ {}", indent, cause);
}
}
println!("\nPretty printed:");
pretty_print_chain(&err);
}Custom formatting helps present errors clearly.
Chain vs root_cause
use anyhow::{anyhow, Context, Result};
fn main() -> Result<()> {
let err = anyhow!("Root")
.context("Level 1")
.context("Level 2")
.context("Level 3");
// root_cause() gives just the innermost error
println!("Root cause: {}", err.root_cause());
// chain() gives all errors including intermediate ones
println!("\nFull chain:");
for cause in err.chain() {
println!(" {}", cause);
}
// chain() is more flexible when you need all context
// root_cause() is convenient when you only need the original error
// Example: Check if root cause is a specific type
use std::io;
let io_err = io::Error::new(io::ErrorKind::NotFound, "file missing");
let wrapped = anyhow::Error::from(io_err)
.context("Could not read config")
.context("Initialization failed");
// Check root cause type
match wrapped.root_cause().downcast_ref::<io::Error>() {
Some(io_err) => {
println!("IO error kind: {:?}", io_err.kind());
}
None => {
println!("Not an IO error");
}
}
Ok(())
}Use root_cause() for the deepest error; use chain() for full context.
Debug Printing
use anyhow::{anyhow, Context, Result};
fn main() {
let err = anyhow!("Something went wrong")
.context("In operation A")
.context("During process B")
.context("While executing C");
// {:?} shows the chain
println!("Debug format:\n{:?}", err);
// {:#?} shows pretty-printed chain
println!("\nPretty debug format:\n{:#?}", err);
// Regular Display just shows the outermost
println!("\nDisplay format:\n{}", err);
}The Debug format includes the full chain automatically.
Practical Error Reporting
use anyhow::{Context, Result};
use std::fs;
fn load_user_config(username: &str) -> Result<String> {
let path = format!("/home/{}/config", username);
fs::read_to_string(&path)
.context(format!("Could not read config for user '{}'", username))?
.lines()
.nth(0)
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("Config file is empty"))
.context("Invalid configuration")
}
fn initialize_app(username: &str) -> Result<()> {
let config = load_user_config(username)
.context("Application initialization failed")?;
println!("Loaded config: {}", config);
Ok(())
}
fn report_error(err: &anyhow::Error) {
eprintln!("Error: {}", err);
if err.chain().count() > 1 {
eprintln!("\nError chain:");
for (i, cause) in err.chain().enumerate() {
eprintln!(" {}: {}", i, cause);
}
}
// Also print source locations if available
eprintln!("\nBacktrace:");
for cause in err.chain() {
if let Some(bt) = cause.backtrace() {
eprintln!(" {}: has backtrace", cause);
}
}
}
fn main() {
if let Err(err) = initialize_app("testuser") {
report_error(&err);
}
}Comprehensive error reporting helps users understand failures.
Collecting Chain Information
use anyhow::{anyhow, Context, Result};
fn analyze_error(err: &anyhow::Error) -> Vec<String> {
err.chain()
.map(|e| e.to_string())
.collect()
}
fn main() -> Result<()> {
let err = anyhow!("Base")
.context("Level A")
.context("Level B")
.context("Level C");
// Collect into vector
let causes = analyze_error(&err);
println!("Collected {} causes", causes.len());
for (i, cause) in causes.iter().enumerate() {
println!(" {}: {}", i, cause);
}
// Check chain properties
let has_multiple_causes = err.chain().count() > 1;
let depth = err.chain().count();
println!("\nChain depth: {}", depth);
println!("Has multiple causes: {}", has_multiple_causes);
// Join all causes into single string
let full_message: String = err.chain()
.map(|e| e.to_string())
.collect::<Vec<_>>()
.join(" -> ");
println!("Full chain: {}", full_message);
Ok(())
}Chain can be collected and processed like any iterator.
Working with Error Sources
use anyhow::{anyhow, Context, Result};
use std::error::Error;
fn show_error_tree(err: &anyhow::Error, indent: usize) {
println!("{}{}", " ".repeat(indent), err);
// Chain gives us all causes flattened
// But we can also work with the Error trait's source()
if let Some(source) = err.source() {
show_error_tree_recursive(source, indent + 1);
}
}
fn show_error_tree_recursive(err: &dyn Error, indent: usize) {
println!("{}{}", " ".repeat(indent), err);
if let Some(source) = err.source() {
show_error_tree_recursive(source, indent + 1);
}
}
fn main() {
let err = anyhow!("Root error")
.context("Context 1")
.context("Context 2");
println!("Using chain():");
for cause in err.chain() {
println!(" {}", cause);
}
println!("\nUsing Error::source() recursively:");
show_error_tree(&err, 0);
// Both approaches show the same chain,
// but chain() is more idiomatic for anyhow
}chain() is the idiomatic way to traverse anyhow error causes.
Synthesis
Quick reference:
use anyhow::{anyhow, Context, Result};
// Create an error chain
let err = anyhow!("root error")
.context("first context")
.context("second context");
// Iterate through all causes
for cause in err.chain() {
println!("{}", cause);
}
// Get just the root cause
let root = err.root_cause();
// Count causes
let depth = err.chain().count();
// Find specific cause
let found = err.chain().find(|c| c.to_string().contains("keyword"));
// Collect into vector
let causes: Vec<_> = err.chain().collect();
// Format for display
let formatted: String = err.chain()
.map(|c| c.to_string())
.collect::<Vec<_>>()
.join(" → ");
// Key points:
// - chain() iterates from outermost to innermost
// - root_cause() returns the innermost (original) error
// - Each context() call wraps the previous error
// - chain() includes both contexts and the root error
// - Chain implements Iterator<&dyn Error>Key insight: Chain bridges anyhow's error handling with Rust's standard Error trait. The chain() method returns an iterator that walks through each layer of context added via .context(), ending with the root cause. This is essential for error reporting—you want to show users not just that something failed, but the full narrative of why it failed. Each context adds a layer of explanation: "Transaction failed" → "Query timeout" → "Connection refused" → "Network unreachable". The chain preserves this narrative, making errors debuggable and actionable. Use chain() when you need the full story; use root_cause() when you only need to identify the underlying error type.
