---
title: How to Use the anyhow Crate for Application Errors
summary: Use the anyhow crate to simplify Rust error handling by returning anyhow::Result and propagating errors with the ? operator.
lang: en
slug: how-to-use-the-anyhow-crate-for-application-errors
published: 2026-04-17T12:05:42+00:00
updated: 2026-04-25T22:00:13+00:00
canonical: https://www.rustfaq.org/en/how-to-use-the-anyhow-crate-for-application-errors/
---

# How to Use the anyhow Crate for Application Errors



## When error types collide

You are building a command-line tool that reads a config file, parses JSON, and fetches data from an API. The first function fails if the file is missing. The second fails if the JSON is malformed. The third fails if the network drops. You write the first function and return `Result<String, std::io::Error>`. The second returns `Result<Data, serde_json::Error>`. The third returns `Result<Response, reqwest::Error>`.

Now you need a function that calls all three. The compiler demands a single error type. You try `Result<(), std::io::Error>`, but the JSON error does not fit. You try `Box<dyn std::error::Error>`, and suddenly every function signature looks like a legal contract written in a foreign language. You spend more time wrestling with error types than solving the actual problem.

## The universal adapter

`anyhow` solves this by providing a single error type that can hold any other error. Think of it like a universal power adapter. You have a laptop charger, a phone charger, and a camera charger, each with a different plug shape. Instead of carrying three adapters, you carry one universal adapter that accepts any plug and outputs standard power. `anyhow::Error` is that adapter. It wraps whatever error you throw at it and presents a uniform interface.

The crate implements `From` for nearly every standard library error and most common third-party errors. This means the `?` operator works without extra code. You write `file.read()?`, and if `read` fails with an `io::Error`, `anyhow` catches it, wraps it, and returns an `anyhow::Error`. The caller sees one consistent type. The error details are preserved inside the wrapper.

Use `anyhow` in application crates where you control the top-level error handling. It is designed for binaries, CLI tools, and scripts. It is not designed for libraries. Libraries need to expose specific error types so callers can recover programmatically. `anyhow` hides those types behind a dynamic wrapper. Keep `anyhow` in the app. Keep `thiserror` in the library.

## Minimal example

Add `anyhow = "1"` to your `Cargo.toml`. Import `anyhow::Result` and the `anyhow!` macro. Define functions to return `Result` types that automatically convert other errors.

```rust
use anyhow::{anyhow, Result};

/// Reads a config file and returns its contents.
/// Returns an anyhow::Result which can hold any error type.
fn read_config() -> Result<String> {
    // std::fs::read_to_string returns Result<String, std::io::Error>.
    // The ? operator automatically converts io::Error to anyhow::Error.
    let content = std::fs::read_to_string("config.txt")?;

    // Custom errors need the anyhow! macro to convert to anyhow::Error.
    // The macro supports formatting arguments like println!.
    if content.is_empty() {
        return Err(anyhow!("Config file is empty"));
    }

    Ok(content)
}

fn main() -> Result<()> {
    // main can return Result<()> to let errors print to stderr automatically.
    // The runtime handles the error display, so you don't need a match block.
    let config = read_config()?;
    println!("Config: {}", config);
    Ok(())
}
```

The community convention is to import `anyhow::Result` and use it directly. Writing `Result<T, anyhow::Error>` works but is verbose. Everyone reads `Result<T>` and knows it means "anyhow result" in application code. Also, use the `anyhow!` macro for custom messages. It is shorter and supports formatting arguments. Avoid `anyhow::Error::msg` unless you have a specific reason.

Let `?` do the heavy lifting. Write `anyhow!` only when you have a message to add.

## How the chain works

Under the hood, `anyhow::Error` is a smart pointer. It points to a heap allocation that holds the error chain. The chain is a linked list of errors. Each node has a message and a source error. When you print the error, `anyhow` walks the chain and formats it.

When you call `read_config()`, the function returns `Result<String, anyhow::Error>`. Inside, `std::fs::read_to_string` might fail. The `?` operator checks the result. If it is `Ok`, it unwraps the string. If it is `Err`, the operator calls `From::from` on the error. `anyhow` implements `From<std::io::Error>`, so the IO error gets wrapped inside an `anyhow::Error` and returned immediately. If the file is empty, `anyhow!("Config file is empty")` creates a new `anyhow::Error` with that message.

The error type is opaque to the caller. The caller does not know if the error came from the filesystem or the empty check. It just knows something went wrong. This opacity is a feature. It prevents callers from depending on specific error types. It forces you to handle errors at the boundary where you have enough context to decide what to do.

`anyhow::Error` requires the inner error to implement `Send` and `Sync`. This means you can send `anyhow::Error` across threads. This is crucial for async code. If an error does not implement `Send`, `anyhow` will not accept it. You will get a compiler error about trait bounds, usually `E0277` (trait bound not satisfied). Most standard errors implement `Send + Sync`, so this rarely bites you.

The error chain preserves the root cause. When you print an `anyhow::Error`, you see the full chain. The message appears first, followed by the source errors. This makes debugging easier. You get the context you added plus the original error details.

Context turns a cryptic "No such file" into a helpful "Failed to load config: No such file".

## Realistic usage with context

In real code, you often need to add descriptive messages to errors. `anyhow` provides the `Context` trait for this. The method `.context("message")` wraps the error and attaches the message. This creates a chain. When printed, you see "Failed to read config.json: No such file or directory".

```rust
use anyhow::{anyhow, Context, Result};

/// Represents a user configuration.
struct Config {
    name: String,
    timeout: u64,
}

/// Loads and parses the configuration.
/// Uses Context to add descriptive messages to errors.
fn load_config() -> Result<Config> {
    // Read the file. Context adds "Failed to read config" to the error chain.
    // The original io::Error is preserved as the source.
    let content = std::fs::read_to_string("config.json")
        .context("Failed to read config.json")?;

    // Parse JSON. Context adds "Failed to parse JSON" to the error chain.
    // serde_json::Error is wrapped and linked to the context message.
    let config: Config = serde_json::from_str(&content)
        .context("Failed to parse config.json as JSON")?;

    // Validate fields. Custom error with context.
    // Use anyhow! for the message, then chain it with context.
    if config.timeout == 0 {
        return Err(anyhow!("Timeout cannot be zero"))
            .context("Invalid configuration values");
    }

    Ok(config)
}

fn main() -> Result<()> {
    // Match on the result to handle success and failure explicitly.
    // This is useful when you want custom error handling logic.
    match load_config() {
        Ok(config) => println!("Loaded config for {}", config.name),
        Err(e) => {
            // anyhow prints the full error chain with context.
            // eprintln! sends the error to stderr, which is standard practice.
            eprintln!("Error: {}", e);

            // You can iterate the chain to inspect each link.
            // skip(1) skips the top-level error to show causes.
            for cause in e.chain().skip(1) {
                eprintln!("  Caused by: {}", cause);
            }
        }
    }
    Ok(())
}
```

The community prefers `.context()` over creating a new error with `anyhow!` when you want to preserve the original error. Use `.context()` to wrap existing errors. Use `anyhow!` to create new errors from scratch. Use `.with_context(|| "message")` if the message is expensive to compute. The closure is only called if an error occurs.

`anyhow` works seamlessly with async functions. You can return `Result<T>` from `async fn`. The error type is the same. The `?` operator works inside async blocks. Just ensure the errors you wrap implement `Send + Sync`. Most HTTP client errors and IO errors do.

```rust
use anyhow::Result;

/// Fetches data asynchronously.
/// anyhow::Result works seamlessly with async functions.
async fn fetch_data() -> Result<String> {
    // reqwest::Error implements Send + Sync, so it works with anyhow.
    // The ? operator propagates the error as anyhow::Error.
    let response = reqwest::get("https://api.example.com/data")
        .await
        .context("Failed to fetch data")?;

    // Read the response body.
    let text = response.text().await
        .context("Failed to read response body")?;

    Ok(text)
}
```

Async code does not change the rules. Use `anyhow` the same way. Add context. Propagate with `?`. Handle at the top level.

## Pitfalls and boundaries

Never use `anyhow` in a library crate. Libraries should define their own error types or use `thiserror`. `anyhow` hides the specific error type behind a dynamic wrapper. If your library returns `anyhow::Error`, the caller cannot check if the error is a specific type. They cannot recover from it programmatically. They only get a string. This breaks the contract between library and user.

You cannot pattern match on `anyhow::Error` to check for specific error types. The error is opaque. If you write `match err { MyError::Timeout => ... }`, the compiler rejects it with `E0004` (non-exhaustive patterns) or type mismatch because `anyhow::Error` does not expose its inner type. You must use `downcast_ref` if you absolutely need to inspect the error, but this defeats the purpose of `anyhow`. Downcasting is a last resort. It requires knowing the exact type and breaks if the library changes its error implementation.

If you import `anyhow::Result` and also use `std::result::Result` in the same scope, the compiler gets confused. You will see `E0428` (a type named `Result` is already in scope). Always use `use anyhow::Result;` in application code and avoid importing `std::result::Result` explicitly. Rely on the prelude for standard results if needed, or use fully qualified paths.

`anyhow` adds a small runtime cost. The error chain is allocated on the heap. The `From` implementations add a layer of indirection. For most applications, this cost is negligible. Error handling is rarely on the hot path. If you are writing performance-critical code that generates millions of errors per second, measure first. You might need a custom error type with zero-cost allocation. For 99% of code, `anyhow` is fast enough.

Libraries define errors. Applications handle them. Keep `anyhow` in the app.

## Choosing the right tool

Use `anyhow` in application crates where you control the top-level error handling and want concise code. Use `anyhow` when you are writing a binary, a CLI tool, or a script and need to propagate errors from many different sources without defining a custom error enum. Use `thiserror` in library crates where you need to expose specific error types to callers so they can match and recover. Use `std::io::Error` or simple `Result<T, E>` when the error domain is narrow and well-defined, like a single module that only deals with file operations. Use `Box<dyn std::error::Error>` only when you cannot depend on external crates and need a generic error type, though `anyhow` is preferred when dependencies are allowed.

Pick the tool that matches your boundary. Applications get `anyhow`. Libraries get `thiserror`.

## Where to go next

- [How to use anyhow crate in Rust error handling](/en/how-to-use-anyhow-crate-in-rust-error-handling/)
- [How to use anyhow crate](/en/how-to-use-anyhow-crate/)