TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

Rust: Enums to Wrap Multiple Errors

100 pointsby jasimover 3 years ago

11 comments

shepmasterover 3 years ago
I&#x27;ll hype my own library, SNAFU [1].<p>It simplifies constructing your own &quot;leaf&quot; errors and streamlines the ability of collecting multiple types of errors while attaching more context to them (e.g. filenames, stack traces, user ids, etc.). It allows you to smoothly switch from &quot;stringly-typed&quot; errors to strongly-typed errors. You can create opaque errors to avoid leaking internal implementation details into your public API.<p>Applied to the code in the post:<p><pre><code> use snafu::prelude::*; use std::{fs::File, io::prelude::*}; #[derive(Debug, Snafu)] enum Error { #[snafu(display(&quot;Unable to open {filename}&quot;))] Opening { source: std::io::Error, filename: String, }, #[snafu(display(&quot;Unable to read {filename}&quot;))] Reading { source: std::io::Error, filename: String, }, #[snafu(display(&quot;Unable to parse {buffer} as a number&quot;))] Parsing { source: std::num::ParseIntError, buffer: String, }, } fn read_number_from_file(filename: &amp;str) -&gt; Result&lt;u64, Error&gt; { let mut file = File::open(filename).context(OpeningSnafu { filename })?; let mut buffer = String::new(); file.read_to_string(&amp;mut buffer) .context(ReadingSnafu { filename })?; let buffer = buffer.trim(); let parsed: u64 = buffer.parse().context(ParsingSnafu { buffer })?; Ok(parsed) } </code></pre> The key parts are the `derive(Snafu)` on the definition of the error enum and the usages of `.context` and `XxxSnafu` at the error sites.<p>Importantly, this example demonstrates a key feature of SNAFU, here shown as &quot;not all `io::Error`s are the same&quot;. Opening the file and reading the file are two separate error conditions and should <i>not</i> be lumped together as one.<p>[1]: <a href="https:&#x2F;&#x2F;docs.rs&#x2F;snafu&#x2F;0.7.0-beta.1&#x2F;snafu&#x2F;index.html" rel="nofollow">https:&#x2F;&#x2F;docs.rs&#x2F;snafu&#x2F;0.7.0-beta.1&#x2F;snafu&#x2F;index.html</a>
评论 #28802194 未加载
评论 #28801396 未加载
评论 #28801295 未加载
nagisaover 3 years ago
The fact that failure conditions from both `File::open` and `read_to_string` become an `IoError` is a significant roadblock to making these errors useful. The mechanism as described in this blog post also fails to introduce contextual information about the reason a failure has occurred.<p>This means that errors, if implemented as described in this post, either formatted or handled don&#x27;t give sufficient information to the caller&#x2F;user on how to deal with the error.<p>EDIT:<p>I strongly recommend to use `op.map_err(|e| SomeError::Open(e, filename))?` and `op.map_err(SomeError::Read)?` as an alternative when propagating the errors. It is more typing at the location of propagation than just `?`, but the errors this approach produces are immediately actionable regardless of whether they are printed to the user or handled by a caller. Provided, of course, this pattern is applied consistently.
评论 #28800429 未加载
评论 #28801388 未加载
评论 #28800782 未加载
dljsjrover 3 years ago
Propagation without needing to use boxed trait objects can also be accomplished using `anyhow`[1]&#x2F;`eyre`[2], which have nice downcasting API’s for recovering the original error type if you know what the possibilites could be. I only bring it up because they aren’t mentioned until the end of the article and only in passing but they offer really nice features for attaching context and downcasting that makes up for the pseudo-type-erasure.<p>1: <a href="https:&#x2F;&#x2F;github.com&#x2F;dtolnay&#x2F;anyhow" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;dtolnay&#x2F;anyhow</a><p>2: <a href="https:&#x2F;&#x2F;github.com&#x2F;yaahc&#x2F;eyre" rel="nofollow">https:&#x2F;&#x2F;github.com&#x2F;yaahc&#x2F;eyre</a>
评论 #28800819 未加载
aazaaover 3 years ago
I find this kind of article that explains how to do something with vanilla Rust valuable because many articles will instead explain how to use a crate that offers similar functionality. This is the only way to know if a crate can actually pull its weight before you decide to adopt it.
Machaover 3 years ago
For reducing the code to implement this pattern, `thiserror` is nice. `derive_more` also has similar functionality, and I tend to use that more personally because I use its other derives, and while thiserror has a slightly nicer API (derive_more will assume a unit struct&#x27;s field is it&#x27;s source, which is wrong more often than not in my codebase, since I avoid overdoing nested error enums), it&#x27;s not worth adding another dep to do what the dep I already have can do: <a href="https:&#x2F;&#x2F;jeltef.github.io&#x2F;derive_more&#x2F;derive_more&#x2F;error.html" rel="nofollow">https:&#x2F;&#x2F;jeltef.github.io&#x2F;derive_more&#x2F;derive_more&#x2F;error.html</a>
评论 #28801029 未加载
c7DJTLrnover 3 years ago
Error handling in Rust is one of the reasons I went back to Go. It&#x27;s a lot of mental overhead having to constantly think about all the different types of errors from different crates that could be returned and how to handle them at the caller. I don&#x27;t want to have to import somebody&#x27;s hobby crate that claims to make it easier either. In Go it&#x27;s just dead simple.<p>I&#x27;ve noticed a lot of Rust discussion is on how to write the code or make the compiler happy which is quite telling. I don&#x27;t want to spend my time thinking about how to write the code and make the compiler happy - I want to get a problem solved and a ticket closed. Hence why I went back to Go (but I acknowledge these languages have distinct goals).
评论 #28804991 未加载
评论 #28806392 未加载
评论 #28804608 未加载
评论 #28806470 未加载
shaonerover 3 years ago
I faced the same kind of issue lately and thought that implementing a From trait for each type of error was kind of annoying.<p>Taking the article example, I ended up doing this:<p><pre><code> #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum MyError { MyErr1, MyErr2, MyErr3, } fn read_number_from_file(filename: &amp;str) -&gt; Result&lt;u64, MyError&gt; { let mut file = File::open(filename).or(Err(MyError::MyErr1))?; &#x2F;&#x2F; Error! let mut buffer = String::new(); file.read_to_string(&amp;mut buffer).or(Err(MyError::MyErr2))?; &#x2F;&#x2F; Error let parsed: u64 = buffer.trim().parse().or(Err(MyError::MyErr3))?; &#x2F;&#x2F; Error Ok(parsed) } </code></pre> As I&#x27;m a beginner, I would love to hear some thoughts on this
评论 #28801700 未加载
评论 #28802392 未加载
评论 #28801690 未加载
评论 #28804278 未加载
ragneseover 3 years ago
Something worth mentioning is that your error types do NOT have to implement std::error::Error. The type parameters on Result&lt;T, E&gt; have no constraints.<p>So, only your public API should implement std::error::Error.<p>Likewise, you don&#x27;t HAVE TO implement From for all of your error types. In fact, it has some of the same concerns that implicit constructors in C++ have in that it&#x27;s easy to just throw try-operators around without thinking about whether you actually want to bubble up an error in a specific scenario.<p>I think a lot of people who pick up Rust might not realize that and end up thinking the error defining process is even more tedious than it really is.
ejanusover 3 years ago
Beyond Borrow checker, move&#x2F;copy, and error propagation(different error in fn), what else should someone learn in order to have basic understanding of Rust?
schneemsover 3 years ago
Errors in rust are one of the hardest things for me to wrap my head around as a beginner. Thanks for this post!<p>I also really like this blog post that also walks you through the history of some features like the try! Macro and shows how they are implemented <a href="https:&#x2F;&#x2F;blog.burntsushi.net&#x2F;rust-error-handling&#x2F;" rel="nofollow">https:&#x2F;&#x2F;blog.burntsushi.net&#x2F;rust-error-handling&#x2F;</a>
quotemstrover 3 years ago
More hacks upon hacks to make up for the glaring inability of the Rust people to acknowledge they made a mistake by leaving out exceptions. Just like the &quot;type after identifier&quot; thing, the use of error values instead of exceptions was a pointless piece of Go envy in fashion at the time that we&#x27;re all going to have to live with for decades.<p>Plus, Rust doesn&#x27;t even get to avoid paying for exceptions, because it still has panics and still has to support unwind semantics --- even though they can be disabled at build time.<p>Avoiding exceptions was a needless technical error and an illustration of why we should design systems around tried and true techniques instead of jumping on fashion trends and ossifying those fashion trends into immutable mediocrity.
评论 #28801260 未加载
评论 #28801274 未加载
评论 #28801831 未加载
评论 #28802292 未加载
评论 #28802538 未加载
评论 #28801179 未加载
评论 #28802271 未加载