https://blog.rust-lang.org/2025/09/12/crates-io-phishing-cam...
Your answers on stack overflow and your crates have helped me so much. Thank you!
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.This means that each function only cares about its own error, and how to generate it. And doesn’t require macros. Just thiserror.
I do too! I've been debating whether I should update SNAFU's philosophy page [1] to mention this explicitly, and I think your comment is the one that put me over the edge for "yes" (along with a few child comments). Right now, it simply says "error types that are scoped to that module", but I no longer think that's strong enough.
[1]: https://docs.rs/snafu/latest/snafu/guide/philosophy/index.ht...
No dogma. If you want an error per module that seems like a good way to start, but for complex cases where you want to break an error down more, we'll often have an error type per function/struct/trait.
The Rust playground uses GitHub Gists as the primary storage location for shared data. I'm dreading the day that I need to migrate everything away from there to something self-maintained.
You can write code using the actor model with Tokio. But it's not natural to do so.
I tend to write most of my async Rust following the actor model and I find it natural. Alice Rhyl, a prominent Tokio contributor, has written about the specific patterns:
https://ryhl.io/blog/actors-with-tokio/