Expand description

Details about the output of the Snafu macro

This procedural macro:

  • produces the corresponding context selectors
  • implements the Error trait
  • implements the Display trait
  • implements the ErrorCompat trait

Detailed example

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

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

    #[snafu(display("Could not open config"))]
    SaveConfig { source: std::io::Error },

    #[snafu(display("The user id {user_id} is invalid"))]
    UserIdInvalid { user_id: i32, backtrace: Backtrace },

    #[snafu(display("Could not validate config with key {key}: checksum was {checksum}"))]
    ConfigValidationFailed {
        checksum: u64,
        key: String,
        source: crypto::Error,
    },
}

Generated code

Note — The actual generated code may differ in exact names and details. This section is only intended to provide general guidance. Use a tool like cargo-expand to see the exact generated code for your situation.

Context selectors

Each enum variant will generate an additional type called a context selector:

struct OpenConfigSnafu<P> {
    filename: P,
}

struct SaveConfigSnafu<P>;

struct UserIdInvalidSnafu<I> {
    user_id: I,
}

struct ConfigValidationFailedSnafu {
    checksum: u64,
    key: String,
    source: crypto::Error,
}

Notably:

  1. The name of the selector is the enum variant’s name with the suffix Snafu added. If the name originally ended in Error, that is removed.
  2. The source and backtrace fields have been removed; the library will automatically handle these for you.
  3. Each remaining field’s type has been replaced with a generic type.
  4. If there are no fields remaining for the user to specify, the selector will not require curly braces.

If the original variant had a source field, its context selector will have an implementation of IntoError:

impl<P> IntoError<Error> for OpenConfigSnafu<P>
where
    P: Into<PathBuf>,

Otherwise, the context selector will have the inherent methods build and fail and can be used with the ensure macro:

impl<I> UserIdInvalidSnafu<I>
where
    I: Into<i32>,
{
    fn build(self) -> Error { /* ... */ }

    fn fail<T>(self) -> Result<T, Error> { /* ... */ }
}

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

Error

Error::source will return the underlying error, if there is one:

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Error::OpenConfig { source, .. } => Some(source),
            Error::SaveConfig { source, .. } => Some(source),
            Error::UserIdInvalid { .. } => None,
            Error::ConfigValidationFailed { source, .. } => Some(source),
        }
    }
}

Error::cause will return the same as source. As Error::description is soft-deprecated, it will return a string matching the name of the variant.

Display

Every field of the enum variant is made available to the format string, even if they are not used:

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter ) -> fmt::Result {
        match self {
            Error::OpenConfig { filename, source } =>
                write!(f, "Could not open config at {}", filename.display()),
            Error::SaveConfig { source } =>
                write!(f, "Could not open config"),
            Error::UserIdInvalid { user_id, backtrace } =>
                write!(f, "The user id {} is invalid", user_id = user_id),
            Error::ConfigValidationFailed { key, checksum, source } =>
                write!(f, "Could not validate config with key {}: checksum was {}", key = key, checksum = checksum),
        }
    }
}

If no display format is specified, the variant’s name will be used by default. If the field is an underlying error, that error’s Display implementation will also be included.

ErrorCompat

Every variant that carries a backtrace will return a reference to that backtrace.

impl snafu::ErrorCompat for Error {
    fn backtrace(&self) -> Option<&Backtrace> {
        match self {
            Error::OpenConfig { .. } => None,
            Error::SaveConfig { .. } => None,
            Error::UserIdInvalid { backtrace, .. } => Some(backtrace),
            Error::ConfigValidationFailed { .. } => None,
        }
    }
}