[][src]Crate snafu

SNAFU

Design philosophy

SNAFU believes that it should be easy to bin one underlying error type (such as io::Error) into multiple domain-specific errors while also optionally adding contextual information.

SNAFU is designed to be used in libraries, not just end-user applications.

Quick example

This example mimics a (very poor) authentication process that opens a file, writes to a file, and checks the user's ID. While two of our operations involve an io::Error, these are different conceptual errors to us.

SNAFU creates context selectors mirroring each error variant. These are used with the context method to provide ergonomic error handling.

use snafu::{Snafu, ResultExt, Backtrace, ErrorCompat};
use std::{fs, path::{Path, PathBuf}};

#[derive(Debug, Snafu)]
enum Error {
    #[snafu_display("Could not open config from {}: {}", "filename.display()", "source")]
    OpenConfig { filename: PathBuf, source: std::io::Error },
    #[snafu_display("Could not save config to {}: {}", "filename.display()", "source")]
    SaveConfig { filename: PathBuf, source: std::io::Error },
    #[snafu_display("The user id {} is invalid", "user_id")]
    UserIdInvalid { user_id: i32, backtrace: Backtrace },
}

type Result<T, E = Error> = std::result::Result<T, E>;

fn log_in_user<P>(config_root: P, user_id: i32) -> Result<bool>
where
    P: AsRef<Path>,
{
    let config_root = config_root.as_ref();
    let filename = &config_root.join("config.toml");

    let config = fs::read(filename).context(OpenConfig { filename })?;
    // Perform updates to config
    fs::write(filename, config).context(SaveConfig { filename })?;

    if user_id != 42 {
        UserIdInvalid { user_id }.fail()?;
    }

    Ok(true)
}

fn log_in() {
    match log_in_user(CONFIG_DIRECTORY, USER_ID) {
        Ok(true) => println!("Logged in!"),
        Ok(false) => println!("Not logged in!"),
        Err(e) => {
            eprintln!("An error occurred: {}", e);
            if let Some(backtrace) = ErrorCompat::backtrace(&e) {
                println!("{}", backtrace);
            }
        }
    }
}

The Snafu macro

This procedural macro implements the Error trait and produces the corresponding context selectors.

Detailed example

use snafu::Snafu;
use std::path::PathBuf;

#[derive(Debug, Snafu)]
enum Error {
    #[snafu_display("Could not open config at {}: {}", "filename.display()", "source")]
    OpenConfig { filename: PathBuf, source: std::io::Error },
    #[snafu_display("Could not open config: {}", "source")]
    SaveConfig { source: std::io::Error },
    #[snafu_display("The user id {} is invalid", "user_id")]
    UserIdInvalid { user_id: i32 },
}

Generated code

This will generate three additional types called context selectors:

This example is not tested
struct OpenConfig<P> { filename: P }
struct SaveConfig<P> { filename: P }
struct UserIdInvalid<I> { user_id: I }

Notably:

  1. One struct is created for each enum variant.
  2. The name of the struct is the same as the enum variant's name.
  3. The source and backtrace fields have been removed; the library will automatically handle this for you.
  4. Each remaining field's type has been replaced with a generic type.

If the original variant had a source field, its context selector will have an implementation of From for a snafu::Context:

This example is not tested
impl<P> From<Context<Error, OpenConfig<P>>> for Error
where
    P: Into<PathBuf>,

Otherwise, the context selector will have an inherent method fail:

This example is not tested
impl<I> UserIdInvalid<I>
where
    I: Into<i32>,
{
    fn fail<T>(self) -> Result<T, Error> { /* ... */ }
}

If the original variant had a backtrace field, the backtrace will be automatically constructed when either From or fail are called.

Attributes

Controlling Display

For backwards compatibility purposes, there are a number of ways you can specify how the Display trait will be implemented for each variant:

  • #[snafu::display("a format string with arguments: {}", info)]

    No special escaping is needed; this looks just like the arguments to a call to println!.

  • #[snafu_display("a format string with arguments: {}", "info")]

    Every argument is quoted as a string literal separately.

  • #[snafu_display = r#"("a format string with arguments: {}", info)"#]

    The entire

Each choice has the same capabilities. All of the fields of the variant will be available and you can call methods on them, such as filename.display().

Version compatibility

SNAFU is tested and compatible back to Rust 1.18, released on 2017-06-08. Compatibility is controlled by Cargo feature flags.

Default

  • Targets the current stable version of Rust at the time of release of the crate. Check the Cargo.toml for the exact version.

No features - supports Rust 1.18

  • Implements Error and Display.
  • Creates context selectors.

rust_1_30 - supports Rust 1.30

  • Adds an implementation for Error::source
  • Adds support for re-exporting the Snafu macro directly from the snafu crate.

unstable_display_attribute - supports Rust Nightly

  • Adds support for the snafu::display attribute.

Other feature flags

backtraces

When enabled, you can use the Backtrace type in your enum variant. If you never use backtraces, you can omit this feature to speed up compilation a small amount.

Re-exports

pub use snafu_derive::Snafu;

Structs

Backtrace

A backtrace starting from the beginning of the thread.

Context

A combination of an underlying error and additional information about the error. It is not expected for users of this crate to interact with this type.

Traits

ErrorCompat

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

ResultExt

Additions to Result.