Huh?
#[derive(Debug, thiserror::Error)]
enum CustomError {
#[error("failed to open a: {0}")]
A(std::io::Error),
#[error("failed to open b: {0}")]
B(std::io::Error),
}
fn main() -> Result<(), CustomError> {
std::fs::read_to_string("a").map_err(CustomError::A)?;
std::fs::read_to_string("b").map_err(CustomError::B)?;
Ok(())
}
If I understand correctly, the main feature of snafu is "merely" reducing the boilerplace when adding context: low_level_result.context(ErrorWithContextSnafu { context })?;
// vs
low_level_result.map_err(|err| ErrorWithContext { err, context })?;
But to me, the win seems to small to justify the added complexity. low_level_result.context(ErrorWithContextSnafu { context })?;
low_level_result.map_err(|err| CustomError::ErrorWithContext { err, context })?;
Other small details:- You don't need to move the inner error yourself.
- You don't need to use a closure, which saves a few characters. This is even true in cases where you have a reference and want to store the owned value in the error:
#[derive(Debug, Snafu)]
struct DemoError { source: std::io::Error, filename: PathBuf }
let filename: &Path = todo!();
result.context(OpenFileSnafu { filename })?; // `context` will change `&Path` to `PathBuf`
- You can choose to capture certain values implicitly, such as a source file location, a backtrace, or your own custom data (the current time, a global-ish request ID, etc.)----
As an aside:
#[error("failed to open a: {0}")]
It is now discouraged to include the text of the inner error in the `Display` of the wrapping error. Including it leads to duplicated data when printing out chains of errors in a nicer / structured manner. SNAFU has a few types that work to undo this duplication, but it's better to avoid it in the first place.
Your answers on stack overflow and your crates have helped me so much. Thank you!