anyhow ist das Gegenstück zu thiserror: das idiomatische Crate für Application-Errors in Rust. Wo thiserror strukturierte Enums für Library-Code generiert, bietet anyhow einen dynamischen Error-Typ (anyhow::Error), der praktisch alles aufnehmen kann. Plus: einen einfachen Mechanismus, Errors mit Kontext anzureichern (.context("beim Laden der Config")), Macros für Inline-Errors (bail!, ensure!), und eine sehr ergonomische API für CLI-Programme und Binaries. Anyhow ist designt für Code, dessen Konsumenten nur einen Menschen sind — keine programmatische Differenzierung nötig, aber jede Diagnose-Information willkommen.

Setup

Wie thiserror ist anyhow ein externes Crate:

Rust Cargo.toml
[dependencies]
anyhow = "1.0"

Sehr leichtgewichtig, in der Rust-Community extrem verbreitet (eines der meist-genutzten Crates überhaupt). Funktioniert problemlos zusammen mit thiserror — die beiden ergänzen sich (mehr im Strategie-Artikel).

anyhow::Error — der universelle Error-Typ

Das Kern-Konzept von anyhow ist ein dynamischer Error-Typ, der jeden Error aufnehmen kann.

Rust anyhow::Error
use anyhow::Result;

fn lese_und_parse(pfad: &str) -> Result<i32> {
    let inhalt = std::fs::read_to_string(pfad)?;       // io::Error → anyhow::Error
    let n: i32 = inhalt.trim().parse()?;               // ParseIntError → anyhow::Error
    Ok(n)
}

fn main() -> Result<()> {
    let n = lese_und_parse("/tmp/wert")?;
    println!("{n}");
    Ok(())
}

anyhow::Result<T> ist ein Type-Alias für Result<T, anyhow::Error>. Der ?-Operator funktioniert auf jedem fallible-Aufruf, weil anyhow::Error eine Blanket-Impl From<E: Error + Send + Sync + 'static> hat — jeder Stdlib-Error (und jeder thiserror-Error, jeder Library-Error) wird automatisch konvertiert.

Damit hast du in einer Funktion eine einheitliche Rückgabe, egal aus wie vielen Quellen Fehler kommen. Im Gegenzug verlierst du die Typ-Information: aus Konsumenten-Sicht ist jeder Fehler nur ein anyhow::Error, ohne dass du per Match auf bestimmte Varianten reagieren könntest.

Für Application-Code (Top-Level-Programme, CLI-Tools, Skripte) ist das genau die richtige Wahl: der einzige Konsument ist der Mensch, der das Programm ausführt, und der will eine sprechende Fehler-Nachricht — keine programmatische Differenzierung.

Context — die wichtigste Methode

Das mächtigste Feature von anyhow ist der Context-Trait: er ermöglicht das nachträgliche Anreichern von Fehlern mit Kontext-Information.

Rust Context
use anyhow::{Context, Result};

fn lade_config(pfad: &str) -> Result<String> {
    std::fs::read_to_string(pfad)
        .context(format!("Beim Laden der Config-Datei '{pfad}'"))
}

fn main() -> Result<()> {
    let config = lade_config("/etc/myapp.conf")?;
    println!("{config}");
    Ok(())
}

Context::context(self, "kontext") ist eine Erweiterungs-Methode auf jedem Result. Bei Ok lässt sie den Wert unverändert; bei Err wickelt sie den Fehler in einen neuen anyhow-Error mit der Kontext-Nachricht als zusätzlicher Ebene. Damit entsteht eine Error-Kette, in der jeder Layer Kontext anreichert.

Das Beispiel-Output (bei einer fehlenden Datei):

Error: Beim Laden der Config-Datei '/etc/myapp.conf'

Caused by:
    No such file or directory (os error 2)

Der oberste Layer ist der Kontext-String, darunter die original Stdlib-Fehler-Nachricht. Bei mehreren context()-Anreicherungen entsteht eine entsprechend tiefe Chain.

with_context — lazy Kontext

Rust with_context
use anyhow::{Context, Result};

fn lade(pfad: &str) -> Result<String> {
    std::fs::read_to_string(pfad)
        .with_context(|| format!("Beim Laden der Datei '{pfad}'"))
}

with_context(closure) ist die lazy Variante. Die Closure wird nur bei Fehler aufgerufen — bei Ok läuft die Format-Operation nicht. Bei teuren Kontext-Berechnungen (etwa format! mit vielen Argumenten) ist with_context die effizientere Wahl.

Faustregel: bei billigen Kontext-Strings (Konstanten, einfache Strings) reicht context. Bei format!-Ausdrücken oder anderen Berechnungen ist with_context korrekt.

Mehrere Context-Layer

Rust Multi-Layer
use anyhow::{Context, Result};
use std::fs;

fn lade_user(id: u64) -> Result<String> {
    let pfad = format!("/data/users/{id}.json");
    let inhalt = fs::read_to_string(&pfad)
        .with_context(|| format!("Lese User-Datei {pfad}"))?;

    let json: serde_json::Value = serde_json::from_str(&inhalt)
        .with_context(|| format!("Parse JSON für User {id}"))?;

    let name = json["name"].as_str()
        .ok_or_else(|| anyhow::anyhow!("Feld 'name' fehlt"))
        .with_context(|| format!("Extraktion für User {id}"))?;

    Ok(name.to_string())
}

Mehrere context()-Schichten ergeben eine mehrstufige Fehler-Chain. Bei einem Fehler im JSON-Parsen siehst du im Output: „Lese User-Datei /data/users/42.json" → „Parse JSON für User 42" → Caused by: „expected ':' at line 5". Jede Schicht erklärt, was sie versuchte; der Endpunkt ist der eigentliche Stdlib-Fehler.

Diese Art von Diagnose-Information ist Gold wert beim Debuggen produktiver Probleme. Ohne anyhow müsstest du sie mühsam von Hand mit map_err und String-Concat aufbauen.

bail! und ensure! — Inline-Errors

Zwei Macros vereinfachen das Werfen von Fehlern:

Rust bail! / ensure!
use anyhow::{bail, ensure, Result};

fn validiere(alter: i32, name: &str) -> Result<()> {
    // bail! ist ein Macro für `return Err(anyhow::anyhow!(...))`
    if alter < 0 {
        bail!("Alter darf nicht negativ sein: {alter}");
    }

    // ensure! ist wie assert, aber mit anyhow-Error statt panic
    ensure!(!name.is_empty(), "Name darf nicht leer sein");
    ensure!(name.len() < 50, "Name '{name}' zu lang (max 50 Zeichen)");

    Ok(())
}

bail!("nachricht") ist ein Macro, das einem return Err(anyhow::anyhow!("nachricht")) entspricht. Es spart die explizite return Err(...)-Schreibweise.

ensure!(bedingung, "nachricht") ist das Pendant zu assert! mit anyhow-Error statt panic. Wenn die Bedingung false ist, wird die Funktion mit einem anyhow-Error verlassen. Sehr kompakt für Pre-Condition-Checks in fallible Funktionen.

Beide Macros unterstützen die normale Format-Syntax — du kannst Werte in die Nachricht einbetten.

anyhow! für ad-hoc-Errors

Rust anyhow! Makro
use anyhow::{anyhow, Result};

fn finde_user(id: u64) -> Result<String> {
    if id == 0 {
        return Err(anyhow!("User-ID 0 ist reserviert"));
    }
    Ok(format!("User-{id}"))
}

anyhow!("nachricht") ist das Bau-Makro für anyhow-Errors. Es ist die explizite Form, die bail! intern nutzt. Praktisch, wenn du den Error als Wert in einer Variable brauchst oder ihn in einer Closure verwendest.

main mit anyhow

Das idiomatische CLI-Pattern:

Rust main mit anyhow
use anyhow::{Context, Result};
use std::fs;

fn main() -> Result<()> {
    let args: Vec<String> = std::env::args().collect();
    let pfad = args.get(1).context("Bitte Pfad angeben")?;

    let inhalt = fs::read_to_string(pfad)
        .with_context(|| format!("Beim Lesen von {pfad}"))?;

    println!("{inhalt}");
    Ok(())
}

fn main() -> anyhow::Result<()> ist die Standard-Signatur für anyhow-basierte CLI-Programme. Bei einem Fehler wird der gesamte Error-Chain als Debug-Output ausgegeben, der Prozess endet mit Exit-Code 1.

Die Ausgabe bei einem Fehler sieht typischerweise so aus:

Error: Beim Lesen von /tmp/foo

Caused by:
    No such file or directory (os error 2)

Das ist End-User-freundlich genug für die meisten CLI-Anwendungen. Für noch detailliertere Fehler-Ausgaben kannst du RUST_BACKTRACE=1 setzen — anyhow integriert sich mit Stdlib-Backtraces.

Konvertierung zwischen anyhow und konkreten Errors

anyhow::Error kann zurück in konkrete Typen konvertiert werden — über downcast.

Rust downcast
use anyhow::Result;
use std::io;

fn process() -> Result<()> {
    std::fs::read_to_string("/tmp/foo")?;
    Ok(())
}

fn main() {
    if let Err(e) = process() {
        // Versuche, den Original-Typ zurückzubekommen
        if let Some(io_err) = e.downcast_ref::<io::Error>() {
            if io_err.kind() == io::ErrorKind::NotFound {
                eprintln!("Datei fehlt — wird neu angelegt");
                return;
            }
        }
        eprintln!("Unerwarteter Fehler: {e:?}");
    }
}

Error::downcast_ref::<T>() versucht, eine Referenz auf den ursprünglichen Typ zu bekommen. Bei Erfolg Some(&T), sonst None. Damit kannst du selektiv auf bestimmte Original-Fehler reagieren, auch wenn dein generischer Error-Typ anyhow ist.

Es gibt auch downcast<T>() (verbrauchend, gibt Result<T, anyhow::Error> zurück) für Cases, wo du den Original-Typ besitzen willst. In der Praxis ist downcast_ref häufiger.

Praxis: anyhow im echten Code

Typischer CLI-Helfer

Rust CLI-Verarbeitung
use anyhow::{bail, Context, Result};
use std::fs;
use std::path::Path;

pub fn kopiere_datei(quelle: &str, ziel: &str) -> Result<u64> {
    let src = Path::new(quelle);
    if !src.exists() {
        bail!("Quell-Datei '{quelle}' existiert nicht");
    }

    let bytes = fs::copy(quelle, ziel)
        .with_context(|| format!("Beim Kopieren von '{quelle}' nach '{ziel}'"))?;

    Ok(bytes)
}

Klassisches CLI-Pattern: explizite Vorbedingungs-Prüfung mit bail!, gefolgt von I/O-Operation mit Context-Anreicherung. Bei einem Fehler bekommt der User eine sehr klare Nachricht.

Verschachtelte Operationen mit Layer-Kontext

Rust Multi-Step
use anyhow::{Context, Result};
use std::fs;

pub fn migriere_daten(quelle_dir: &str, ziel_dir: &str) -> Result<usize> {
    let eintraege = fs::read_dir(quelle_dir)
        .with_context(|| format!("Lese Quell-Verzeichnis '{quelle_dir}'"))?;

    let mut count = 0;
    for eintrag in eintraege {
        let eintrag = eintrag
            .with_context(|| format!("Lese Eintrag in '{quelle_dir}'"))?;
        let name = eintrag.file_name();

        let ziel = format!("{ziel_dir}/{}", name.to_string_lossy());
        fs::copy(eintrag.path(), &ziel)
            .with_context(|| format!("Kopiere {:?} nach {ziel}", eintrag.path()))?;

        count += 1;
    }

    Ok(count)
}

Bei verschachtelten Operationen entsteht eine reichhaltige Error-Chain. Wenn etwa beim 7. Eintrag das Kopieren scheitert, siehst du im Output: „Kopiere /src/datei_7.txt nach /dst/datei_7.txt" → Caused by: „Permission denied". Sofort weißt du, wo das Problem liegt.

Domain-Validierung mit ensure!

Rust Validierung
use anyhow::{ensure, Result};

pub struct UserInput {
    pub email: String,
    pub age: u32,
}

pub fn validiere(input: &UserInput) -> Result<()> {
    ensure!(!input.email.is_empty(), "Email darf nicht leer sein");
    ensure!(input.email.contains('@'), "Email '{}' ungültig (kein @)", input.email);
    ensure!(input.age >= 18, "Alter {} zu jung (mindestens 18)", input.age);
    ensure!(input.age <= 120, "Alter {} unrealistisch", input.age);
    Ok(())
}

ensure! macht Validierungs-Code sehr kompakt — ein Liner pro Prüfung, mit eingebauter Fehler-Nachricht. Bei Multi-Prüfungen wie hier ist das deutlich lesbarer als explizite if/return Err-Konstrukte.

Result-Type-Alias mit anyhow

Rust Type-Alias
// In main.rs der Application:
pub type AppResult<T> = anyhow::Result<T>;

// Funktions-Signaturen werden noch kompakter:
pub fn lade_user(id: u64) -> AppResult<String> {
    anyhow::ensure!(id > 0, "ID muss positiv sein");
    Ok(format!("User-{id}"))
}

Auch bei anyhow lohnt sich oft ein Type-Alias — entweder AppResult<T> oder einfach Result<T> mit Local-Re-Export. Macht die Signatur-Zeilen kürzer.

Downcast für selektive Behandlung

Rust Downcast-Pattern
use anyhow::Result;
use std::io;

pub fn lese_oder_default(pfad: &str) -> Result<String> {
    match std::fs::read_to_string(pfad) {
        Ok(s) => Ok(s),
        Err(e) if e.kind() == io::ErrorKind::NotFound => {
            Ok(String::from("default-content"))
        }
        Err(e) => Err(e.into()),
    }
}

// Oder: Behandlung am Aufruf-Ort mit downcast
pub fn behandle(pfad: &str) -> Result<String> {
    let result = std::fs::read_to_string(pfad);
    match result {
        Ok(s) => Ok(s),
        Err(e) => {
            if e.kind() == io::ErrorKind::NotFound {
                Ok(String::from("default"))
            } else {
                Err(e.into())
            }
        }
    }
}

downcast ist die Möglichkeit, in anyhow-Code doch noch typ-spezifisch zu reagieren. Aber: oft ist die direkte Stdlib-Variante (vor der anyhow-Konvertierung) klarer. Erst wenn der Error schon durch viele anyhow-Layers gegangen ist, lohnt sich downcast.

Kombination mit thiserror

Rust Library + App
use anyhow::{Context, Result};
use thiserror::Error;

// Library-Error mit thiserror
#[derive(Debug, Error)]
pub enum DbError {
    #[error("Connection-Fehler: {0}")]
    Connection(String),
    #[error("Query-Fehler: {0}")]
    Query(String),
}

// Library-Funktion
pub fn db_query(query: &str) -> std::result::Result<String, DbError> {
    if query.is_empty() {
        return Err(DbError::Query("leere Query".into()));
    }
    Ok(format!("Result für '{query}'"))
}

// Application-Funktion mit anyhow
pub fn lade_user(id: u64) -> Result<String> {
    let query = format!("SELECT name FROM users WHERE id = {id}");
    let result = db_query(&query)
        .with_context(|| format!("Lade User mit ID {id}"))?;
    Ok(result)
}

Die idiomatische Kombi: Library nutzt thiserror für strukturierte Errors, Application nutzt anyhow für kontextreiche Fehler-Chains. Der ?-Operator konvertiert automatisch (anyhow akzeptiert jeden Error), with_context reichert mit Application-Layer-Kontext an.

Async-Code mit anyhow

Rust Async
// (Pseudo-Code — erfordert tokio + reqwest)
// use anyhow::{Context, Result};
//
// pub async fn fetch_data(url: &str) -> Result<String> {
//     let response = reqwest::get(url)
//         .await
//         .with_context(|| format!("HTTP-Request an {url}"))?;
//
//     let body = response.text()
//         .await
//         .context("Body-Decoding")?;
//
//     Ok(body)
// }

In Async-Code arbeitet anyhow genauso wie sync. Die Send + Sync-Bounds sind via anyhow::Error automatisch erfüllt — Errors können über await-Punkte und Task-Grenzen wandern.

Logging mit Error-Chain

Rust Logging
use anyhow::Result;

pub fn run() -> Result<()> {
    // ... Logic ...
    Ok(())
}

fn main() {
    if let Err(e) = run() {
        // Debug-Output zeigt die volle Chain
        eprintln!("Fehler: {e:?}");
        std::process::exit(1);
    }
}

{e:?} (Debug-Output) zeigt die komplette Error-Chain mit allen Layern. {e} (Display) nur die oberste Nachricht. Für End-User-Logging meist {e:?} verwenden, weil die Chain die meiste Information enthält.

FAQ

anyhow ist für Application-Code.

Top-Level-Programme, CLI-Tools, Skripte, Binaries. Der einzige Konsument ist der Mensch, der das Programm ausführt. Differenzierung verschiedener Fehler-Klassen ist nicht nötig — Kontext und Diagnose-Information schon.

thiserror für Library, anyhow für Application.

Die Standard-Aufteilung in der Rust-Welt. Library-Errors sollen strukturiert und programmatisch behandelbar sein (thiserror). Application-Errors sollen ergonomisch zu schreiben und kontextreich sein (anyhow).

anyhow::Result als Type-Alias.

Steht für Result<T, anyhow::Error>. Macht Signatur-Zeilen kompakter. In main ist die Form fn main() -> anyhow::Result<()> der CLI-Standard.

.context("...") reichert Errors an.

Das wichtigste Feature. Bei Err wickelt es den Fehler in einen neuen anyhow-Error mit dem Kontext als zusätzlicher Ebene. Bei Ok passiert nichts. Damit entstehen reichhaltige Error-Chains für Debugging.

.with_context(|| ...) ist die lazy Variante.

Bei teuren Kontext-Berechnungen (z. B. format! mit vielen Argumenten) korrekt — die Closure läuft nur bei Error. Bei billigen Strings reicht context().

bail!("...") und ensure!(cond, "...").

Macros für Inline-Errors. bail! ist return Err(anyhow!(...)). ensure! ist assert! mit anyhow-Error statt panic. Sehr kompakt für Validierungs-Code.

downcast_ref() für selektive Behandlung.

Wenn du in anyhow-Code doch typ-spezifisch auf einen bestimmten Error reagieren willst, geht das per downcast. In der Praxis selten gebraucht; meist ist die direkte Variante (vor der anyhow-Konvertierung) klarer.

Debug-Output zeigt die Error-Chain.

{e:?} mit anyhow::Error zeigt nicht nur den obersten Fehler, sondern die komplette Kette mit allen context()-Layers und der ursprünglichen Quelle. Für End-User-Logging meist die richtige Wahl.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Error Handling

Zur Übersicht