That was a pleasure to read. Well written, nicely formatted and good links to further reading topics.
The only missing piece was talking about https://github.com/dtolnay/thiserror which I would expect to be prominently featured given how prevalent it is
And possibly https://github.com/dtolnay/anyhow which is arguably a simpler form of "error handling" but sometimes that's all you need—although probably not in the Finance or Space industries ;-)
The article does talk about anyhow (maybe it was updated?) but yeah, no thiserror seems like a big omission. thiserror is how you solve the problem of exposing your own error types in your public API without pulling your hair out.
> Surprisingly, the type wrapped by std::result::Result::Err doesn't need an Error bound:
None of the errors types I create implements the trait Error. I'm just using simple enums with various variants and that's sufficient for me. The crates that I develop are mostly binary crate [1] so implementing the trait Error is maybe more prevalent in libraries crate. Reading the article, it feels a little overkill to me. I like the simple, straightforward approach (the one that you learn in the Book). I don't use anyhow also, I actively try to limit the dependencies I'm using.
The biggest thing to me is that many people lump errors by type (“all IO errors are equivalent”) which is very wrong to me. SNAFU encourages and streamlines bucketing your errors into detailed chunks.
Along the way, it makes adding details to your error easier (automatic calls to Into without a closure; optional implicit details).
It combines string-like errors (anyhow) and structured errors (thiserror) in one library.
It has basic command line error formatting that shows the causal chain without duplication.
I don’t use eyre so I don’t feel comfortable comparing to it.
Is there a way snafu could be redesigned not to rename Error to Snafu in custom error variants? It's annoying for like IDE reasons, but maybe technically challenging to fix.
I think you are asking for this config [0] (let me know if not, maybe provide an example), but I’d caution against renaming it back to “Error”. The context selectors are not themselves errors, they are more-or-less fancy error constructors. It’s the same logic I’d use to call it `Error::new` instead of `Error::error`.
Having the context selectors called the exact same as the variants was the way the original version of SNAFU worked, but it was also the number one cause of confusion and support requests.
Automatically generates structs for typing error contexts instead of just having strings. This forms a chain of causes, each of which contains additional context about what was happening when the error was returned.
I personally have used it because it can provide backtraces in stable Rust. It also can be used either similarly to anyhow (with the `whatever` macro) or more like thiserror (with explicit errors as enum variants). I don't have experience with eyre so can't provide a comparison there.
If Rust had an equivalent to tuples but for sum types I believe the error ergonomics would be better. This would result in more exact error handling instead of a crate wide error enum that has error states that would never happen. Not to mention the chore of constructing enum wrappers for all error cases.
I partially agree. I have been experimenting with creating a lot of small error types (e.g. one per function) because of the benefit of narrowing down the set of possible causes. I also dislike crate-wide errors for the same reason.
However, having an anonymous sum type isn’t a good solution here because there’s nowhere for you to add context to. It’s not useful for your final error to say “permission denied” without the context of a file name, or knowing that you were trying to open configuration file, etc.
Also, depending on your implementation, you either cannot allow multiple errors of the same underlying cause (e.g. two IO errors, one for writing and one for opening) because they are the same type or you have positional error tuples (error index 0 is write, index 1 is open). Neither of those is super ergonomic.
Oh look, my old friend Checked Exceptions! I knew we would meet again one day :)
In all seriousness, anonymous sum types would be quite nice. It would make it easier to be precise about return values in situations where `Optional` and `Result` are not sufficient.
The problem with checked exceptions (well, one of the problems) is that the full set of error conditions a function might encounter is often _too_ detailed. Encoding them all into the type signature of every function can be a lot of work, and quite brittle to minor internal changes. So most code bases will still need a catch-call error type that gets bubbled up to a generic handler somewhere.
I am not a rustefarian so take my opinion on that for what it's worth: i.e. not much. [I tried the language about 5 years ago and I was disturbed by the dbus crate overloading of the dereference operator, to Rust practitioners I ask is &*msg.interface() an axiomatic Rust construct ?]
From what I understand from the article, returning a dyn Error in a Result appears to be about as useful as throwing a new Exception("something bad happened") in java. I am sure it gets better in part two "Structured Rust errors" but this guide on error handling doesn't increase my motivation to give Rust another chance.
I don’t know the dbus crate, so I’m not trying to say anything about that crate’s usage, I’m simply saying that referencing after dereferencing is idiomatic Rust.
For example, the standard library `String` type dereferences to `str` which you can then re-reference to `&str`.
Sorry, I should have been clearer in my question, I wanted to know about A deref to B re-ref to &B.
ameliaquining awnsered my question and you modulated my opinion, that pattern is can be idiomatic when the principle of minimal surprises is respected.
Thank you for that link, it exactly describes my experience and the rational behind my opinion:
The Deref trait is designed for the implementation of custom pointer types. The intention is that it will take a pointer-to-T to a T, not convert between different types. It is a shame that this isn’t (probably cannot be) enforced by the trait definition.
Rust tries to strike a careful balance between explicit and implicit mechanisms, favouring explicit conversions between types. Automatic dereferencing in the dot operator is a case where the ergonomics strongly favour an implicit mechanism, but the intention is that this is limited to degrees of indirection, not conversion between arbitrary types.
Given that `Interface` is "A wrapper around a string that is guaranteed to be a valid D-Bus interface name", it's not surprising that derefering it would provide the underlying `String`. And then it's “deref-coerced” for you into &str. &str and &String are different types but it's not a conversion between arbitrary types. It's definitely not magic. Personally, I think it is reasonable and quite helpful. It would be a shame if this was the whole reason you gave up on Rust 5 years ago, I hope it wasn't!
That and the always changing crates required since the std lib was anemic and the stupidily long compilation time.
But I must admit that Cargo and clippy were nice and lifetime are easier to understand than when using std::move in c++ is required vs when it's implicit.
But really what made me abandon learning Rust is Nim. My first Nim program I made a small maze game in about 200 lines.
Also the fact that I now mostly program recreationally since I pivoted from software development to infrastructure ( my title is system analyst) at a research University (absolute job security is really nice). Software development at that institution was getting to political to my taste and I like to play with nice hardware!
The only missing piece was talking about https://github.com/dtolnay/thiserror which I would expect to be prominently featured given how prevalent it is
And possibly https://github.com/dtolnay/anyhow which is arguably a simpler form of "error handling" but sometimes that's all you need—although probably not in the Finance or Space industries ;-)