7 ms·
Guide to Error Handling in Rust
- shepmaster 2y agoFor that haven’t seen it, I’ll drop in my library SNAFU https://docs.rs/snafu/latest/snafu/ https://docs.rs/snafu/latest/snafu/
- ameliaquining 2y agoCan you briefly explain why I might want to use this instead of anyhow or eyre?
- xvedejas 2y agoI 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.
- shepmaster 2y agoDo note that the standard library has stabilized backtraces now, so you only need that if you are targeting an older version of Rust. https://doc.rust-lang.org/std/backtrace/struct.Backtrace.html https://doc.rust-lang.org/std/backtrace/struct.Backtrace.htm...
- shepmaster 2y agoThe 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.
- xvedejas 2y agoIs 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.
- shepmaster 2y agoI 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. [0]: https://docs.rs/snafu/latest/snafu/derive.Snafu.html#changing-the-context-selector-suffix https://docs.rs/snafu/latest/snafu/derive.Snafu.html#changin...
- iknowstuff 2y ago.context(ReticulatingSnafu { spline: 42 })? 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.
- airstrike 2y agoThat 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 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 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 ;-)
- bobbylarrybobby 2y agoThe 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.
- deleted 2y ago[deleted]
- nick__m 2y agoI 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.
- shepmaster 2y agoYes, `&*foo` is meaningful. For example, `Vec<T>` becomes `[T]` and then `&[T]`. Yes, a `Box<dyn Error>` is about the most rudimentary way of returning an arbitrary error, but it’s better than a panic or a sentinel value.
- nick__m 2y agoYour example is not what the dbus crate does, in your case everything is related to T, it doesn't surprise! In that crate the &*msg.interface().unwrap() magically become an &str !
- shepmaster 2y agoI 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`.
- nick__m 2y agoSorry, 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.
- hyujwfasdf 2y agoIf 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. https://youtrack.jetbrains.com/issue/KT-68296/Union-Types-for-Errors https://youtrack.jetbrains.com/issue/KT-68296/Union-Types-fo... https://harelang.org/tutorials/introduction#defining-new-error-types https://harelang.org/tutorials/introduction#defining-new-err...
- shepmaster 2y agoI 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.
- UnquietTinkerer 2y agoOh 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.
- jicea 2y ago> 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. [1]: https://github.com/Orange-OpenSource/hurl https://github.com/Orange-OpenSource/hurl
- avinassh 2y agoside note: (assuming you are the author) could you please add RSS feed to your blog? thank you!
- eventhelix 2y agoI am not the author. You could request this in the comments section on the page.