Loading page…
Rust walkthroughs
Loading page…
anyhow::Context::context enhance error messages with additional debugging information?anyhow::Context::context transforms bare errors into rich, contextual error chains by attaching human-readable descriptions at each point where an error propagates. Instead of seeing just "No such file or directory," you see "failed to open config file: No such file or directory," then "failed to load application settings: failed to open config file: No such file or directory," building a causal chain that shows exactly where the error originated and what was being attempted at each layer. The context method works on any Result<T, E> where E implements std::error::Error, automatically converting it into anyhow::Error with the added context, enabling seamless error enrichment without changing function signatures.
use anyhow::{Context, Result};
use std::fs;
fn main() -> Result<()> {
let content = fs::read_to_string("config.json")
.context("Failed to read config file")?;
println!("Config: {}", content);
Ok(())
}.context() wraps the original error with additional information, creating a chain of context.
use anyhow::{Context, Result};
use std::fs;
fn load_config() -> Result<String> {
fs::read_to_string("config.json")
.context("Failed to read config file")
}
fn parse_config(content: &str) -> Result<serde_json::Value> {
serde_json::from_str(content)
.context("Failed to parse config as JSON")
}
fn get_database_url() -> Result<String> {
let content = load_config()
.context("Failed to load application configuration")?;
let config = parse_config(&content)
.context("Failed to process configuration")?;
config["database_url"]
.as_str()
.map(|s| s.to_string())
.ok_or_else(|| anyhow::anyhow!("database_url not found in config"))
.context("Failed to extract database URL from config")
}
fn main() {
match get_database_url() {
Ok(url) => println!("Database URL: {}", url),
Err(e) => {
eprintln!("Error: {}", e);
// Prints the full chain:
// Error: Failed to extract database URL from config
//
// Caused by:
// database_url not found in config
//
// Or for a full chain:
// Error: Failed to load application configuration
//
// Caused by:
// Failed to read config file
//
// Caused by:
// No such file or directory (os error 2)
}
}
}Each .context() call adds a layer to the error chain, showing the full path of failure.
use anyhow::{Context, Result};
use std::fs;
fn main() -> Result<()> {
let filename = "data.txt";
// context: static string
let _content = fs::read_to_string(filename)
.context("Failed to read file")?;
// with_context: closure for dynamic context (lazy evaluation)
let _content = fs::read_to_string(filename)
.with_context(|| format!("Failed to read file: {}", filename))?;
// with_context only evaluates if there's an error
// Useful for expensive context construction
let _content = fs::read_to_string(filename)
.with_context(|| {
// This only runs if read_to_string fails
let cwd = std::env::current_dir().unwrap_or_default();
format!("Failed to read file in {:?}, filename: {}", cwd, filename)
})?;
Ok(())
}context takes a static string; with_context takes a closure for lazy, dynamic context.
use anyhow::{Context, Result};
use std::fs;
fn deep_function() -> Result<()> {
fs::read_to_string("nonexistent.txt")
.context("Layer 1: Reading file")?;
Ok(())
}
fn middle_function() -> Result<()> {
deep_function()
.context("Layer 2: Processing data")?;
Ok(())
}
fn top_function() -> Result<()> {
middle_function()
.context("Layer 3: Application startup")?;
Ok(())
}
fn main() {
match top_function() {
Ok(()) => println!("Success"),
Err(e) => {
// Single line: just the top error
println!("Error: {}", e);
// Debug format: shows causes with backtraces (if enabled)
println!("\nDebug:\n{:?}", e);
// Pretty printing with chain
println!("\nPretty chain:");
for (i, cause) in e.chain().enumerate() {
println!(" {}: {}", i, cause);
}
// Alternative: print all causes
println!("\nFull error:");
eprintln!("{:?}", e); // Uses Debug, shows chain
}
}
}Access the full error chain via .chain() or use debug formatting for complete output.
use anyhow::{Context, Result};
use std::fs;
use std::net::TcpStream;
use std::io::Read;
fn read_config() -> Result<String> {
// IO errors
fs::read_to_string("config.txt")
.context("Failed to read configuration file")?
.parse::<String>()
.context("Failed to parse configuration content")
}
fn connect_server() -> Result<TcpStream> {
// Network errors
TcpStream::connect("127.0.0.1:8080")
.context("Failed to connect to server")
}
fn parse_number(s: &str) -> Result<i32> {
// Parse errors
s.parse::<i32>()
.context(format!("Failed to parse '{}' as number", s))
}
fn main() -> Result<()> {
let config = read_config()
.context("Application initialization failed")?;
let mut stream = connect_server()
.context("Failed to establish server connection")?;
let number = parse_number("not a number")
.context("Failed to process configuration value")?;
Ok(())
}.context() works with any error type that implements std::error::Error.
use anyhow::{Context, Result};
use std::error::Error;
fn operation() -> Result<()> {
std::fs::read_to_string("missing.txt")
.context("Failed to read file")?;
Ok(())
}
fn main() {
match operation() {
Ok(()) => {}
Err(e) => {
// The top-level error
println!("Top error: {}", e);
// The root cause (original error)
if let Some(root) = e.root_cause().downcast_ref::<std::io::Error>() {
println!("Root cause is IO error: {:?}", root);
}
// Navigate the chain manually
let mut source = e.source();
println!("\nError chain:");
let mut level = 1;
while let Some(cause) = source {
println!(" Level {}: {}", level, cause);
source = cause.source();
level += 1;
}
}
}
}Use .root_cause() to find the original error and .source() to traverse the chain.
use anyhow::{Context, Result};
use std::fs;
#[derive(Debug)]
struct FileContext {
path: String,
operation: String,
}
impl std::fmt::Display for FileContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Failed to {} file: {}", self.operation, self.path)
}
}
fn read_user_data(user_id: u32) -> Result<String> {
let path = format!("users/{}.json", user_id);
fs::read_to_string(&path)
.with_context(|| FileContext {
path: path.clone(),
operation: "read".to_string(),
})?
.parse::<String>()
.context("Failed to parse user data content")
}
fn main() {
match read_user_data(42) {
Ok(data) => println!("Data: {}", data),
Err(e) => {
// Can check for specific context types
for cause in e.chain() {
println!("Cause: {}", cause);
}
}
}
}Create custom context types with Display for structured error information.
use anyhow::{Context, Result, Backtrace};
fn inner_function() -> Result<()> {
std::fs::read_to_string("missing.txt")
.context("Failed to read file")?;
Ok(())
}
fn outer_function() -> Result<()> {
inner_function()
.context("Operation failed")?;
Ok(())
}
fn main() {
// Enable backtraces with RUST_BACKTRACE=1 environment variable
match outer_function() {
Ok(()) => {}
Err(e) => {
// Check for backtrace (requires RUST_BACKTRACE=1)
if let Some(bt) = e.backtrace() {
println!("Backtrace:\n{:?}", bt);
}
// Or use Debug format which includes backtrace
println!("Full error with backtrace:\n{:?}", e);
}
}
}Anyhow captures backtraces when RUST_BACKTRACE=1 is set, showing where errors originated.
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
#[derive(Debug)]
struct AppConfig {
database_url: String,
port: u16,
debug: bool,
}
fn load_app_config(path: &Path) -> Result<AppConfig> {
let content = fs::read_to_string(path)
.with_context(|| format!("Failed to read config from {:?}", path))?;
let json: serde_json::Value = serde_json::from_str(&content)
.with_context(|| format!("Failed to parse config JSON from {:?}", path))?;
let database_url = json["database_url"]
.as_str()
.ok_or_else(|| anyhow::anyhow!("Missing database_url"))
.context("Invalid configuration: database_url required")?
.to_string();
let port = json["port"]
.as_u64()
.ok_or_else(|| anyhow::anyhow!("Missing port"))
.with_context(|| format!("Invalid configuration in {:?}", path))?
as u16;
let debug = json["debug"].as_bool().unwrap_or(false);
Ok(AppConfig { database_url, port, debug })
}
fn initialize_app(config_path: &Path) -> Result<()> {
let config = load_app_config(config_path)
.context("Failed to initialize application")?;
println!("App initialized: port={}, debug={}", config.port, config.debug);
Ok(())
}
fn main() -> Result<()> {
initialize_app(Path::new("config.json"))
.context("Application startup failed")
}Layer context at each abstraction level for a clear picture of what failed and where.
use anyhow::{Context, Result, anyhow};
// Function returning std::error::Error type
fn io_operation() -> std::io::Result<()> {
std::fs::read_to_string("missing.txt")?;
Ok(())
}
// Function returning anyhow::Error
fn anyhow_operation() -> Result<()> {
// .context() converts io::Error to anyhow::Error
io_operation()
.context("IO operation failed")?;
Ok(())
}
// Explicit conversion without additional context
fn simple_conversion() -> Result<()> {
// .map_err(|e| anyhow::Error::new(e)) or just use ? with context
io_operation()
.map_err(anyhow::Error::new)?;
Ok(())
}
// Adding context to any Result
fn with_explicit_context() -> Result<()> {
io_operation()
.context("While performing IO operation")?;
Ok(())
}
fn main() {
match anyhow_operation() {
Ok(()) => println!("Success"),
Err(e) => {
println!("Error: {}", e);
for cause in e.chain() {
println!(" caused by: {}", cause);
}
}
}
}.context() converts any std::error::Error into anyhow::Error with added context.
use anyhow::{Context, Result};
use std::fs;
fn read_config_or_default(path: &str) -> Result<String> {
match fs::read_to_string(path) {
Ok(content) => Ok(content),
Err(e) => {
// Log the error with full context
eprintln!("Warning: {:#}", e);
// Return default
Ok("default config".to_string())
}
}
}
fn read_config_with_fallback(primary: &str, fallback: &str) -> Result<String> {
fs::read_to_string(primary)
.context(format!("Failed to read primary config: {}", primary))
.or_else(|e| {
eprintln!("Primary failed, trying fallback: {:#}", e);
fs::read_to_string(fallback)
.context(format!("Failed to read fallback config: {}", fallback))
})
}
fn main() -> Result<()> {
let config = read_config_with_fallback("config.json", "default.json")
.context("Failed to load any configuration")?;
println!("Config: {}", config);
Ok(())
}Use context-aware errors for logging and fallback strategies.
use anyhow::{Context, Result};
fn divide(a: i32, b: i32) -> Result<i32> {
if b == 0 {
Err(anyhow::anyhow!("Division by zero"))
.context(format!("Failed to divide {} by {}", a, b))?
}
Ok(a / b)
}
fn calculate() -> Result<i32> {
divide(10, 0)
.context("Calculation failed")?
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide_error_context() {
let err = divide(10, 0).unwrap_err();
// Check error chain
let chain: Vec<_> = err.chain().collect();
assert!(chain.len() >= 2);
// Check top-level message
assert!(err.to_string().contains("divide"));
assert!(err.to_string().contains("10"));
}
#[test]
fn test_nested_context() {
let err = calculate().unwrap_err();
// Should have multiple levels of context
let chain: Vec<_> = err.chain().collect();
assert!(chain.len() >= 3);
// Check that original error is preserved
assert!(err.root_cause().to_string().contains("zero"));
}
#[test]
fn test_error_message_contains_context() {
let err = calculate().unwrap_err();
let msg = format!("{:?}", err);
// Debug format includes all context
assert!(msg.contains("Calculation"));
assert!(msg.contains("divide"));
}
}Test error chains to verify context is correctly attached.
Method comparison:
| Method | Signature | Use Case |
|--------|-----------|----------|
| context(msg) | &str or String | Static or pre-built context |
| with_context(\|\| msg) | Closure returning String | Dynamic, lazy context |
| ? operator | Propagates with context | Seamless error propagation |
Error chain structure:
Top-level context
├── Layer 2 context
│ ├── Layer 3 context
│ │ └── Root cause (original error)
Display formats:
| Format | Output |
|--------|--------|
| {} (Display) | Top-level error only |
| {:?} (Debug) | Error chain with "Caused by:" |
| {:#?} (Pretty Debug) | Formatted error chain |
| .chain() | Iterator over all causes |
Key insight: anyhow::Context::context enriches errors by wrapping them with contextual information at each propagation point, creating an error chain that tells a story: not just "what failed" but "what was being attempted when it failed." The method works on any Result<T, E> where E: std::error::Error, automatically converting to anyhow::Error and preserving the original error as the root cause. Use .context("static message") for simple strings and .with_context(\|\| format!(...)) for dynamic context that should only be constructed on error. The resulting error chain can be displayed with {:?} for full diagnostics, traversed programmatically via .chain() or .root_cause(), and carries backtraces when RUST_BACKTRACE=1. This pattern is particularly valuable in layered applications where errors bubble up through multiple abstraction boundaries—each layer adds context meaningful to that layer, so a low-level "connection refused" becomes "failed to connect to database" becomes "failed to load user data" becomes "application startup failed." The context chain preserves all this information, making debugging significantly easier than chasing raw error codes.