4 ms·
I suspect you're referring to this article, which is a good read indeed: https://mmapped.blog/posts/12-rust-error-handling https://mmapped.blog/posts/12-rust-er
by YorickPeterse 1y ago
I suspect you're referring to this article, which is a good read indeed: https://mmapped.blog/posts/12-rust-error-handling https://mmapped.blog/posts/12-rust-error-handling
- atombender 1y agoWith that scheme, what about propagating errors upwards? It seems like you have to wrap all "foreign" error types that happen during execution with an explicit enum type. For example, let's say the function frobnicate() writes to a file and might get all sorts of file errors (disk full, no permissions, etc.). It seems like those have to be wrapped or embedded, as the article suggests. But then you can't use the "?" macro, I think? Because wrapping or embedding requires constructing a new error value from the original one. Every single downstream error coming from places like the standard library has to go through a transformation with map_err() or similar.
- remram 1y ago"?" call "into()" automatically, which covers simple wrapping.
- BlackFly 1y agoThe article expressly suggests not wrapping/embedding those errors but instead putting the errors into context. That suggestions starts with the sentence "Define errors in terms of the problem, not a solution" and explicitly shows a wrapping as an anti-pattern then lays out the better solution. Note that the author's solution serializes the underlying error in some cases to avoid leaking it as a dependency. You can use ? when you implement From (to get an automatic Into) which which can be as easy as a #[from] annotation with thiserror. You can manually implement From instead of inlining the map_err if you so choose. Then you are only ever using map_err to pass additional contextual information. You usually end up using ? after map_err or directly returning the result.
- colanderman 1y agoI find that directly propagating errors upwards is an antipattern. In many/most cases, the caller has less information than you do for how to deal with the error. (Your library, after all, is an abstraction, of which such an error is an implementation detail.) It also prevents you from returning your own error condition which isn't one that the downstack error type can represent. It may be that you do in fact have to throw your hands up, and propagate _something_ to the caller; in those cases, I find a Box<dyn Error> or similar better maintains the abstraction boundary.
- mananaysiempre 1y agoIt’s a nice, well-written and well-reasoned article, and yet after shoveling through all the boilerplate it offers as the solution I can’t help but “WTF WTF WTF ...”[1]. [1] https://danluu.com/wat/ https://danluu.com/wat/
- slau 1y agoYes! Thank you!
- conaclos 1y agoThanks for sharing! The only thing I disagree with is error wrapping. While I agree that the main error should not expose dependencies, I find it useful to keep a `cause` field that corresponds to the inner error. It's important to trace the origin of an error and have more context about it. By the way, Rust's [Error] trait has a dedicated `cause` method for that! [Error] https://doc.rust-lang.org/nightly/core/error/trait.Error.html https://doc.rust-lang.org/nightly/core/error/trait.Error.htm...