guide only.Expand description
§Using generic types
Error types enhanced by SNAFU may contain generic type and lifetime parameters.
Construction and diagnostic capabilities have independent bounds.
§Types
#[derive(Debug, Snafu)]
enum Error<T> {
#[snafu(display("The value {value} was too large"))]
TooLarge { value: T, limit: u32 },
#[snafu(display("The value {value} was too small"))]
TooSmall { value: T, limit: u32 },
}
fn validate_number(value: u8) -> Result<u8, Error<u8>> {
ensure!(
value <= 200,
TooLargeSnafu {
value,
limit: 100u32,
},
);
ensure!(
value >= 100,
TooSmallSnafu {
value,
limit: 200u32,
},
);
Ok(value)
}
fn validate_string(value: &str) -> Result<&str, Error<String>> {
ensure!(
value.len() <= 20,
TooLargeSnafu {
value,
limit: 10u32,
},
);
ensure!(
value.len() >= 10,
TooSmallSnafu {
value,
limit: 20u32,
},
);
Ok(value)
}§Lifetimes
#[derive(Debug, Snafu)]
enum Error<'a> {
#[snafu(display("The username {value} contains the bad word {word}"))]
BadWord { value: &'a str, word: &'static str },
}
fn validate_username<'a>(value: &'a str) -> Result<&'a str, Error<'a>> {
ensure!(
!value.contains("stinks"),
BadWordSnafu {
value,
word: "stinks",
},
);
ensure!(
!value.contains("smells"),
BadWordSnafu {
value,
word: "smells",
},
);
Ok(value)
}§Conditional diagnostic capabilities
A generic source does not need to implement Error, Debug, or
Display merely to be stored, matched, or wrapped with context:
use snafu::{ResultExt, Snafu};
#[derive(Debug, Snafu)]
#[snafu(display("read failed after {completed} bytes"))]
struct ReadFailure<E> {
source: E,
completed: usize,
}
fn attach<T, E>(result: Result<T, E>) -> Result<T, ReadFailure<E>> {
result.context(ReadFailureSnafu { completed: 3usize })
}
struct Borrowed<'a>(&'a str); // No diagnostic traits.
let text = String::from("device failure");
let error = attach::<(), _>(Err(Borrowed(&text))).err().unwrap();
assert_eq!(error.to_string(), "read failed after 3 bytes");
assert_eq!(error.source.0, "device failure");
use std::error::Error;
let source = std::io::Error::new(std::io::ErrorKind::Other, "device failure");
let error = attach::<(), _>(Err(source)).err().unwrap();
assert!(error.source().unwrap().is::<std::io::Error>());The generated Error implementation requires Self: Debug + Display
and the ability to expose each source through AsErrorSource. A generic
value source normally needs Error + 'static. Non-static sources can
still be carried as typed fields; this does not change the standard
Error::source signature. Borrowed context fields do not independently
need to be 'static for the outer type to implement Error.
Display is independently conditional on its formatting requirements.
Use display_bounds to replace
inference for complex expressions. Debug remains controlled by its
own implementation: standard derive(Debug) can add conservative bounds,
for example on a host parameter when the field is an associated type.
ErrorCompat also has independent requirements. Retrieving a local
backtrace need not constrain the source, whereas delegating to a source
requires its ErrorCompat implementation. Iterating the standard error
chain additionally requires AsErrorSource.
§Source-aware implicit data
Constructors requesting a backtrace or another implicit field still use
GenerateImplicitData::generate_with_source(&dyn Error) when there is a
source. They retain the required source capability. This exception
preserves source-aware generation; the macro does not silently switch to
generate() when a source cannot be exposed as a standard error.
§Opaque wrappers
A generic opaque wrapper conditionally delegates
Display, Error, and ErrorCompat to the inner type. Construction need
not require any of these capabilities:
use snafu::Snafu;
#[derive(Debug, Snafu)]
#[snafu(source(from(exact)))]
struct ApiError<E>(E);
struct Payload;
let _: ApiError<Payload> = Payload.into();The existing source(from(exact)) option avoids a potentially overlapping
generic From implementation. Exposing a generic opaque type still
exposes its type parameter as part of the public API.
§Inference boundaries
Source-bound inference recognizes references and standard
Box/Rc/Arc spellings, plus the Option used by whatever. It does not
perform name resolution or arbitrary autoderef analysis.
Direct self-recursion, such as source: Box<Failure<T>> inside
Failure<T>, does not introduce a circular bound on the generated
implementation. The source must still be 'static. Formatting and
backtrace delegation likewise avoid direct self-referential predicates.
This detection recognizes the local type name and self::Name;
other qualified paths may need an explicit override.
For aliases, custom smart pointers, or mutually recursive generic types,
use error_bounds and error_compat_bounds
to replace the relevant inferred predicates. These overrides preserve
method-call autoderef in source(); they do not require the pointer
itself to implement Error. A custom type named Box, Rc, or Arc
may also need an override because inference recognizes their spelling,
not their definition.