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:
Oh I do too, and that's one of the recommendations in RFD 400 as well as in my talk. cargo-nextest's runner loop [1] is also structured as two main actors + one for each test. But you have to write it all out and it can get pretty verbose.
The ‘Beware of cycles’ section at the end has some striking similarities with futurelock avoidance recommendations from the original article… not sure what to make of this except to say that this stuff is hard?
You certainly can use thiserror to accomplish the same goals! However, your example does a little subtle slight-of-hand that you probably didn't mean to and leaves off the enum name (or the `use` statement):
- 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.
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.
I accept, however this requires to create many types with corresponding implementations (`impl From`, `impl Display`, ...). This is a lot of boilerplate.
In addition to the sibling comment mentioning thiserror, I also submit my crate SNAFU (linked in my ancestor comment). Reducing some of the boilerplate is a big reason I enjoy using it.
As we all know, Cool URIs don't change [1]. I greatly appreciate the care taken to keep these Compiler Explorer links working as long as possible.
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.
I hope to read through your crate and examples later, but if you have a chance, I’d be curious to hear your take on how Stack Error differs from my library, SNAFU [1]!
I played around a bit with SNAFU a couple of years ago, but I'm haven't worked deeply with the library so there might well be some features I'm not aware of.
I think SNAFU is more like a combination of anyhow and thiserror into a single crate, rather than Stack Error which leans more heavily into the "turnkey" error struct. Using the Whatever struct, you get some overlap with Stack Error features:
- Error message are co-located.
- Error type implement std::error::Error (suitable for library development).
- External errors can be wrapped and context can easily be added.
Where Stack Error differs:
- Error codes (and URIs) offer ability for runtime error handling without having to compare strings.
- Provides pseudo-stack by stacking messages.
Underlying this is an opinion I baked into Stack Error: error messages are for debugging, not for runtime error handling. Otherwise all your error strings effectively become part of your public interface since a downstream library can rely on them for error handling.
SNAFU author here, thanks for including my crate! I’ll try to give your review a thorough read through later and incorporate feedback that makes sense.
I do have https://diataxis.fr/ and related stuff open in another tab and keep meaning to figure out how to best apply it for SNAFU.
Out of curiosity, do you recall if you also read the top-level docs[1]? That’s intended to be the main introduction, I actually don’t expect most people to read the user’s guide, unfortunately.
I agree. Unfortunately, I think that a lot of the people who ask for a bigger standard library really just want (a) someone else to do the work (b) someone they can trust.
The people working on Rust are a finite (probably overextended!) set of people and you can't just add more work to their plate. "Just" making the standard library bigger is probably a non-starter.
I think it'd be great if some group of people took up the very hard work to curate a set of crates that everyone would use and provide a nice façade to them, completely outside of the Rust team umbrella. Then people can start using this Katamari crate to prove out the usefulness of it.
However, many people wouldn't use it. I wouldn't because I simply don't care and am happy adding my dependencies one-by-one with minimal feature sets. Others wouldn't because it doesn't have the mystical blessing/seal-of-approval of the Rust team.
I agree with your general point, but for this specific functionality, I’ll point out that setting environment variables of the current process is unsafe. It took us a long time to realize it so the function wasn’t actually marked as unsafe until the Rust 2024 edition.
What this means in practice is that the call to invoke dotenv should also be marked as unsafe so that the invoker can ensure safety by placing it at the right place.
If no one is maintaining the crate, that won’t happen and someone might try to load environment variables at a bad time.
ok, I'm hooked - how is setting an env var in the current process unsafe? My gut says it's not unsafe in a memory-ownership sense, but rather in a race condition sense?
whatever the issue is, "setting an env var is unsafe" is so interesting to me that I'm now craving a blog post explaining this
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/
reply