[−][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, ensure}; 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 })?; ensure!(user_id == 42, UserIdInvalid { user_id }); 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:
struct OpenConfig<P> { filename: P } struct SaveConfig<P> { filename: P } struct UserIdInvalid<I> { user_id: I }
Notably:
- One struct is created for each enum variant.
- The name of the struct is the same as the enum variant's name.
- The
source
andbacktrace
fields have been removed; the library will automatically handle this for you. - 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
:
impl<P> From<Context<Error, OpenConfig<P>>> for Error where P: Into<PathBuf>,
Otherwise, the context selector will have an inherent method
fail
and can be used with the ensure
macro:
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
andDisplay
. - 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 thesnafu
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; |
Macros
ensure | Ensure a condition is true. If it is not, return from the function with an error. |
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. |
NoneError | A temporary error type used when converting an |
Traits
ErrorCompat | Backports changes to the |
OptionExt | Additions to |
ResultExt | Additions to |