Loading page…
Rust walkthroughs
Loading page…
Anyhow is a crate for idiomatic error handling in Rust applications. It provides anyhow::Error, a trait object-based error type that can hold any error, along with utilities for adding context to errors. Unlike thiserror, which is designed for library code with specific error types, anyhow is designed for application code where you want to easily propagate and annotate errors.
Key concepts:
anyhow::Error — A trait object that can hold any erroranyhow::Result<T> — Alias for Result<T, anyhow::Error>.context() — Add contextual information to errors.with_context() — Add context with closure for lazy evaluationWhen to use Anyhow:
When NOT to use Anyhow:
thiserror)use anyhow::{Result, Context};
use std::fs;
fn read_config() -> Result<String> {
let content = fs::read_to_string("config.toml")?;
Ok(content)
}
fn main() -> Result<()> {
let config = read_config()?;
println!("Config: {}", config);
Ok(())
}use anyhow::{Result, Context};
use std::fs;
fn read_config() -> Result<String> {
let content = fs::read_to_string("config.toml")
.context("Failed to read configuration file")?;
Ok(content)
}
fn load_database() -> Result<()> {
let config = read_config()
.context("Failed to load database configuration")?;
println!("Loaded: {}", config);
Ok(())
}
fn main() -> Result<()> {
load_database()?;
Ok(())
}use anyhow::{Result, Context};
use std::fs;
fn read_user_file(user_id: u64) -> Result<String> {
let path = format!("users/{}.json", user_id);
let content = fs::read_to_string(&path)
.with_context(|| format!("Failed to read user file at '{}'", path))?;
Ok(content)
}
fn main() -> Result<()> {
let content = read_user_file(42)?;
println!("Content: {}", content);
Ok(())
}use anyhow::{Result, Context};
use std::fs;
use std::net::TcpStream;
fn connect_and_read() -> Result<String> {
let mut stream = TcpStream::connect("127.0.0.1:8080")
.context("Failed to connect to server")?;
let config = fs::read_to_string("config.toml")
.context("Failed to read config")?;
Ok(config)
}
fn main() -> Result<()> {
let data = connect_and_read()?;
println!("Data: {}", data);
Ok(())
}use anyhow::{Result, Context};
fn parse_number(input: &str) -> Result<i32> {
input
.parse::<i32>()
.with_context(|| format!("Failed to parse '{}' as integer", input))
}
fn parse_config_value(key: &str, value: &str) -> Result<i32> {
parse_number(value)
.with_context(|| format!("Invalid value for key '{}'", key))
}
fn main() -> Result<()> {
let value = parse_config_value("timeout", "not_a_number")?;
println!("Value: {}", value);
Ok(())
}use anyhow::{Result, anyhow, bail};
fn check_age(age: i32) -> Result<()> {
if age < 0 {
bail!("Age cannot be negative: {}", age);
}
if age > 150 {
bail!("Age seems unrealistic: {}", age);
}
Ok(())
}
fn validate_user(name: &str, age: i32) -> Result<()> {
if name.is_empty() {
return Err(anyhow!("Name cannot be empty"));
}
check_age(age)?;
println!("User {} is valid", name);
Ok(())
}
fn main() -> Result<()> {
validate_user("Alice", 30)?;
validate_user("", 25)?; // Will error
Ok(())
}use anyhow::{Result, ensure};
fn process_file(path: &str) -> Result<()> {
let content = std::fs::read_to_string(path)?;
ensure!(!content.is_empty(), "File is empty");
ensure!(content.len() < 1000, "File too large: {} bytes", content.len());
println!("Processing: {}", content);
Ok(())
}
fn divide(a: i32, b: i32) -> Result<i32> {
ensure!(b != 0, "Division by zero");
Ok(a / b)
}
fn main() -> Result<()> {
process_file("test.txt")?;
let result = divide(10, 2)?;
println!("Result: {}", result);
Ok(())
}use anyhow::{Result, Context};
fn read_config() -> Result<String> {
std::fs::read_to_string("config.toml")
.context("Failed to read config file")
}
fn parse_config(content: &str) -> Result<i32> {
content
.parse()
.context("Failed to parse timeout value")
}
fn get_timeout() -> Result<i32> {
let content = read_config()
.context("Failed to load configuration")?;
let timeout = parse_config(&content)
.context("Failed to parse configuration")?;
Ok(timeout)
}
fn main() -> Result<()> {
match get_timeout() {
Ok(timeout) => println!("Timeout: {}", timeout),
Err(e) => {
// Print full error chain
for cause in e.chain() {
eprintln!("Caused by: {}", cause);
}
}
}
Ok(())
}use anyhow::{Result, Context};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ConfigError {
#[error("missing field: {0}")]
MissingField(String),
#[error("invalid value: {0}")]
InvalidValue(String),
}
fn load_config() -> Result<String> {
// Can propagate thiserror types directly
Err(ConfigError::MissingField("database".into()))?
}
fn main() -> Result<()> {
let config = load_config()
.context("Failed to initialize application")?;
println!("Config: {}", config);
Ok(())
}use anyhow::{Error, Result};
fn convert_error() -> Result<()> {
// From std::io::Error
let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
let anyhow_err: Error = io_err.into();
// From string
let str_err: Error = "something went wrong".into();
// Using anyhow! macro
let msg_err = anyhow!("custom error with {}", "formatting");
Err(msg_err)
}
fn main() {
match convert_error() {
Ok(()) => println!("Success"),
Err(e) => println!("Error: {}", e),
}
}use anyhow::{Result, anyhow};
use std::io;
fn try_operation() -> Result<()> {
Err(io::Error::new(io::ErrorKind::NotFound, "file missing").into())
}
fn main() -> Result<()> {
let result = try_operation();
if let Err(e) = result {
// Check if it's a specific error type
if let Some(io_err) = e.downcast_ref::<io::Error>() {
println!("IO Error: {}", io_err);
if io_err.kind() == io::ErrorKind::NotFound {
println!("File was not found!");
}
}
}
Ok(())
}use anyhow::{Result, Context};
fn find_user(id: u64) -> Result<String> {
let users = vec![(1, "Alice"), (2, "Bob")];
users
.into_iter()
.find(|(uid, _)| *uid == id)
.map(|(_, name)| name.to_string())
.ok_or_else(|| anyhow::anyhow!("User {} not found", id))
}
fn main() -> Result<()> {
let user = find_user(1)?;
println!("Found: {}", user);
let missing = find_user(99);
assert!(missing.is_err());
Ok(())
}use anyhow::{Result, Context};
fn operation() -> Result<()> {
std::fs::read_to_string("missing.txt")
.context("Failed during operation")?;
Ok(())
}
fn main() -> Result<()> {
if let Err(e) = operation() {
// Simple format
println!("Error: {}", e);
// Debug format (includes chain)
println!("Debug: {:?}", e);
// Pretty debug format
println!("Pretty: {:#?}", e);
// Manual chain iteration
println!("\nError chain:");
for (i, cause) in e.chain().enumerate() {
println!(" {}: {}", i, cause);
}
}
Ok(())
}use anyhow::{Result, Context};
use std::fs;
fn load_config() -> Result<String> {
fs::read_to_string("app.toml")
.context("Failed to read app.toml")
}
fn initialize_database(config: &str) -> Result<()> {
// Simulate initialization
if config.is_empty() {
anyhow::bail!("Empty configuration");
}
println!("Database initialized with: {}", config);
Ok(())
}
fn run_application() -> Result<()> {
let config = load_config()
.context("Failed to load configuration")?;
initialize_database(&config)
.context("Failed to initialize database")?;
println!("Application running");
Ok(())
}
fn main() {
if let Err(e) = run_application() {
eprintln!("Error: {:?}", e);
std::process::exit(1);
}
}use anyhow::{Result, Context, ensure};
fn divide(a: f64, b: f64) -> Result<f64> {
ensure!(b != 0.0, "Division by zero");
Ok(a / b)
}
fn parse_positive(input: &str) -> Result<i32> {
let value: i32 = input.parse()
.with_context(|| format!("Failed to parse '{}'", input))?;
ensure!(value > 0, "Value must be positive, got {}", value);
Ok(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide_success() {
assert!(divide(10.0, 2.0).is_ok());
assert_eq!(divide(10.0, 2.0).unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
let result = divide(10.0, 0.0);
assert!(result.is_err());
}
#[test]
fn test_parse_positive() {
assert!(parse_positive("42").is_ok());
assert!(parse_positive("-1").is_err());
assert!(parse_positive("not a number").is_err());
}
}
fn main() -> Result<()> {
let result = divide(10.0, 2.0)?;
println!("Result: {}", result);
Ok(())
}use anyhow::{Result, Context};
#[derive(Debug)]
pub struct HttpResponse {
status: u16,
body: String,
}
fn fetch_user(id: u64) -> Result<HttpResponse> {
// Simulated HTTP response
if id == 0 {
return Err(anyhow::anyhow!("Invalid user ID"));
}
Ok(HttpResponse {
status: 200,
body: format!("{{\"id\": {}, \"name\": \"User\"}}", id),
})
}
fn get_user_name(id: u64) -> Result<String> {
let response = fetch_user(id)
.with_context(|| format!("Failed to fetch user {}", id))?;
anyhow::ensure!(
response.status == 200,
"Unexpected status: {}",
response.status
);
Ok(response.body)
}
fn main() -> Result<()> {
let name = get_user_name(1)?;
println!("User data: {}", name);
Ok(())
}use anyhow::{Result, Context, bail};
enum UserRole {
Admin,
User,
Guest,
}
struct Request {
user_id: Option<u64>,
role: Option<UserRole>,
}
fn handle_request(req: Request) -> Result<String> {
let user_id = req.user_id
.ok_or_else(|| anyhow::anyhow!("Missing user_id"))?;
let role = req.role.ok_or_else(|| anyhow::anyhow!("Missing role"))?;
match role {
UserRole::Admin => Ok(format!("Admin {} handling", user_id)),
UserRole::User => Ok(format!("User {} handling", user_id)),
UserRole::Guest => {
bail!("Guests cannot handle requests");
}
}
}
fn main() -> Result<()> {
let req = Request {
user_id: Some(1),
role: Some(UserRole::Admin),
};
let result = handle_request(req)?;
println!("Result: {}", result);
Ok(())
}Anyhow Key Imports:
use anyhow::{Result, Context, anyhow, bail, ensure};Core Types:
| Type | Description |
|------|-------------|
| anyhow::Error | Trait object error type |
| anyhow::Result<T> | Result<T, anyhow::Error> |
Key Macros:
| Macro | Description |
|-------|-------------|
| anyhow! | Create an error with formatting |
| bail! | Return early with an error |
| ensure! | Assert condition or return error |
Context Methods:
| Method | Description |
|--------|-------------|
| .context(msg) | Add context to error |
| .with_context(\|\| msg) | Add lazy context |
| .chain() | Iterate error causes |
Error Creation:
| Pattern | Example |
|---------|---------|
| From string | anyhow!("error message") |
| With format | anyhow!("error: {}", value) |
| Early return | bail!("fatal error") |
| Assertion | ensure!(x > 0, "must be positive") |
Anyhow vs Thiserror:
| Feature | Anyhow | Thiserror | |---------|--------|----------| | Use case | Applications | Libraries | | Error type | Generic | Specific | | Context | Yes | No | | Matching | Limited | Full | | Boilerplate | Minimal | More |
Key Points:
anyhow::Result<T> for application error handling.context() for better error messagesbail! for early returns with errorsensure! for conditional error returnsthiserror for library errors