Crate snafu

source ·
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.

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

Features

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.

Next steps

Read the documentation for the Snafu macro to see all of the capabilities, then read the user’s guide for deeper understanding.

Re-exports

  • pub use report::__InternalExtractErrorType;

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.
  • An iterator over an Error and its sources that removes duplicated text from the error display strings.
  • The source code location where the error was reported.
  • A temporary error type used when converting an Option into a Result
  • Opinionated solution to format an error in a user-friendly way. Useful as the return type from main and test functions.
  • A basic error type that you can use as a first step to better error handling.

Traits

Attribute Macros

  • Adapts a function to provide user-friendly error output for main functions and tests.

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.