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.