Expand description

SNAFU

SNAFU is a library to easily generate errors and add information to underlying errors, especially when the same underlying error type can occur in different contexts.

Features

For detailed information, please see the user’s guide.

Quick start

If you want to report errors without hassle, start with the Whatever type and the whatever! macro:

use snafu::{prelude::*, Whatever};

fn is_valid_id(id: u16) -> Result<(), Whatever> {
    if id < 10 {
        whatever!("ID may not be less than 10, but it was {}", id);
    }
    Ok(())
}

You can also use it to wrap any other error:

use snafu::{prelude::*, Whatever};

fn read_config_file(path: &str) -> Result<String, Whatever> {
    std::fs::read_to_string(path)
        .with_whatever_context(|_| format!("Could not read file {}", path))
}

Whatever allows for a short message and tracks a Backtrace for every error:

use snafu::{prelude::*, ErrorCompat, Whatever};

fn main() {
    if let Err(e) = returns_an_error() {
        eprintln!("An error occurred: {}", e);
        if let Some(bt) = ErrorCompat::backtrace(&e) {
            eprintln!("{}", bt);
        }
    }
}

Custom error types

Many projects will hit limitations of the Whatever type. When that occurs, it’s time to create your own error type by deriving Snafu!

Struct style

SNAFU will read your error struct definition and create a context selector type (called InvalidIdSnafu in this example). These context selectors are used with the ensure! macro to provide ergonomic error creation:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
#[snafu(display("ID may not be less than 10, but it was {id}"))]
struct InvalidIdError {
    id: u16,
}

fn is_valid_id(id: u16) -> Result<(), InvalidIdError> {
    ensure!(id >= 10, InvalidIdSnafu { id });
    Ok(())
}

If you add a source field to your error, you can then wrap an underlying error using the context extension method:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
#[snafu(display("Could not read file {path}"))]
struct ConfigFileError {
    source: std::io::Error,
    path: String,
}

fn read_config_file(path: &str) -> Result<String, ConfigFileError> {
    std::fs::read_to_string(path).context(ConfigFileSnafu { path })
}

Enum style

While error structs are good for constrained cases, they don’t allow for reporting multiple possible kinds of errors at one time. Error enums solve that problem.

SNAFU will read your error enum definition and create a context selector type for each variant (called InvalidIdSnafu in this example). These context selectors are used with the ensure! macro to provide ergonomic error creation:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("ID may not be less than 10, but it was {id}"))]
    InvalidId { id: u16 },
}

fn is_valid_id(id: u16) -> Result<(), Error> {
    ensure!(id >= 10, InvalidIdSnafu { id });
    Ok(())
}

If you add a source field to a variant, you can then wrap an underlying error using the context extension method:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("Could not read file {path}"))]
    ConfigFile {
        source: std::io::Error,
        path: String,
    },
}

fn read_config_file(path: &str) -> Result<String, Error> {
    std::fs::read_to_string(path).context(ConfigFileSnafu { path })
}

You can combine the power of the whatever! macro with an enum error type. This is great if you started out with Whatever and are moving to a custom error type:

use snafu::prelude::*;

#[derive(Debug, Snafu)]
enum Error {
    #[snafu(display("ID may not be less than 10, but it was {id}"))]
    InvalidId { id: u16 },

    #[snafu(whatever, display("{message}"))]
    Whatever {
        message: String,
        #[snafu(source(from(Box<dyn std::error::Error>, Some)))]
        source: Option<Box<dyn std::error::Error>>,
    },
}

fn is_valid_id(id: u16) -> Result<(), Error> {
    ensure!(id >= 10, InvalidIdSnafu { id });
    whatever!("Just kidding... this function always fails!");
    Ok(())
}

You may wish to make the type Send and/or Sync, allowing your error type to be used in multithreaded programs, by changing dyn std::error::Error to dyn std::error::Error + Send + Sync.

Modules

Additions to the TryFuture and TryStream traits.

SNAFU user’s guide

Traits and macros used by most projects. Add use snafu::prelude::* to your code to quickly get started with SNAFU.

Macros

Ensure a condition is true. If it is not, return from the function with an error.

Ensure a condition is true. If it is not, return a stringly-typed error message.

Constructs a Location using the current file, line, and column.

Instantiate and return a stringly-typed error message.

Structs

A backtrace starting from the beginning of the thread.

An iterator over an Error and its sources.

The source code location where the error was reported.

A temporary error type used when converting an Option into a Result

A basic error type that you can use as a first step to better error handling.

Traits

View a backtrace-like value as an optional backtrace.

Converts the receiver into an Error trait object, suitable for use in Error::source.

Backports changes to the Error trait to versions of Rust lacking them.

Takes a string message and builds the corresponding error.

Construct data to be included as part of an error. The data must require no arguments to be created.

Combines an underlying error with additional information about the error.

Additions to Option.

Additions to Result.

Derive Macros

The Snafu macro is the entrypoint to defining your own error types. It is designed to require little configuration for the recommended and typical usecases while still offering flexibility for unique situations.