.. /index

Snafu

Snafu

Snafu is a Rust error-handling library that generates errors and adds structured information to underlying errors via context selectors. Its core insight is that the same underlying error type (e.g., io::Error) can occur in multiple conceptual situations, and each deserves a distinct, semantically named error variant with structured fields capturing what the program was attempting. It remains a strong choice for large multi-crate workspaces — GreptimeDB, InfluxDB IOx, and Iroh all use it — while thiserror 2.0 holds the position of ecosystem default for simpler projects. See snafu-tutorial for the full tutorial with progressive examples.

Context selectors: the core abstraction

#[derive(Snafu)] generates a context selector type for each error variant — a companion struct that strips source and backtrace fields (SNAFU handles those automatically) and is used to construct errors via .context() and .with_context():

use snafu::prelude::*;
use std::{fs, io, path::PathBuf};

#[derive(Debug, Snafu)]
pub enum Error {
    #[snafu(display("Could not open config at {}", path.display()))]
    OpenConfig { path: PathBuf, source: io::Error },

    #[snafu(display("Could not write output to {}", path.display()))]
    WriteOutput { path: PathBuf, source: io::Error },
}

pub fn load_then_write(config: &PathBuf, out: &PathBuf) -> Result<(), Error> {
    let bytes = fs::read(config).context(OpenConfigSnafu { path: config.clone() })?;
    fs::write(out, bytes).context(WriteOutputSnafu { path: out.clone() })?;
    Ok(())
}

A critical ergonomic detail: selectors call Into::into on each field, so you can pass &str into a String field without explicit conversion. A common beginner mistake is writing .context(Error::Variant { ... }) — you must use the selector (VariantSnafu), not the enum path.

Selector visibility and module-local design

Context selectors are private by default, reflecting SNAFU's opinion that modules should define cohesive error types locally. This reduces accidental coupling between modules. Use #[snafu(visibility(pub))] on the error type or specific variants when cross-module access is needed. Exporting selectors in a public API is warned against — their shape may change across SNAFU versions, creating semver hazards.

For public API stability, SNAFU documents an opaque error type pattern: a public newtype wrapping an internal SNAFU error enum, with delegated traits and a From conversion. This lets you use full SNAFU machinery internally while presenting a stable, minimal error surface to consumers.

Semantic backtraces

Context selectors push codebases toward multiple small, meaningful error types at each architectural layer. The resulting error chain reads as a "semantic backtrace" — not which functions were on the call stack, but what the program was attempting at each layer. This is more debuggable than a raw backtrace, especially in async code where backtraces show executor frames.

Progressive error design

SNAFU explicitly recommends a graduation path:

  1. Whatever + whatever! — turnkey string errors for prototyping. Maximum speed, minimum structure.
  2. Typed enums with context selectors — when you hit limitations or want structured context at call sites.
  3. Opaque newtype wrappers — for public API stability in libraries.

Key ergonomic tools

Backtrace patterns

SNAFU's Backtrace type is env-gated: capture is a no-op unless RUST_BACKTRACE or RUST_LIB_BACKTRACE is set.

Reporting

Display prints only the top-level message (SNAFU's default no longer includes the source's Display, aligning with broader Rust guidance). For full chain + backtrace output:

Async integration

With the futures feature enabled, SNAFU provides TryFutureExt and TryStreamExt traits for attaching context in combinator chains without manual map_err:

fn example() -> impl TryFuture<Ok = i32, Error = Error> {
    another_function().context(AuthenticatingSnafu {
        user_name: "admin",
        user_id: 42,
    })
}

For tracing-error SpanTrace integration, define a local newtype implementing GenerateImplicitData and use #[snafu(implicit)] — see snafu-tutorial for the full pattern.

Boundary conversions

SNAFU errors implement std::error::Error + Send + Sync + 'static, enabling clean boundary conversions:

Version and compatibility

When to prefer Snafu over thiserror

Use Snafu when you need enforced error granularity across a large workspace. The context selector pattern makes it harder to take shortcuts — every .context(SomeVariant) call is visible and reviewable. For application-level errors, smaller projects, or maximum ecosystem familiarity, thiserror is simpler and universally known. For a detailed migration guide from thiserror to SNAFU, see snafu-tutorial.

Comparison with error-stack

Both Snafu and error-stack push toward structured error context, but differently. Snafu's structure lives in your error type definitions (context selectors per variant). error-stack keeps error types simple and builds structure in the report's frame stack at propagation time. Snafu is more natural for library APIs where the error type is the contract; error-stack is more natural for application-level error aggregation with arbitrary attachments.

See rust-error-crate-comparison for the full decision matrix.

Linked from

Sources