Hacker News new | ask | show | jobs
by svnpenn 1318 days ago
This glaring omission from this is the "enum idiom":

https://doc.rust-lang.org/std/convert/trait.From.html#exampl...

they talk about it here:

https://nrc.github.io/error-docs/error-design/error-type-des...

but including more than a snippet would go a long way to that "aha" moment I think. This was frustrating for me browsing this site. The author wrote 10 pages of docs, but nearly all the examples are like 5 line snippets of code. I think examples are equally important as the discussion itself. Rust itself suffers from the same problem:

https://github.com/rust-lang/book/issues/3348

1 comments

Whats the "aha" moment for it, the Froms?

The author might not have included that as they call out you likely shouldn't directly wrap another error.

I go a step further and think that public errors shouldn't have From's for concrete types, exposing your implementation details, and that enum errors are more generally too tied to implementation details to be used in libraries.

> public errors shouldn't have From's for concrete types

> enum errors are more generally too tied to implementation details to be used in libraries

I generally agree. SNAFU addresses these problems in two ways:

1. The `From` implementation is not created for the underlying error but for an intermediate type (by default). That type is private to the crate (by default) and cannot expose implementation details.

2. There's an opaque error facility to completely hide the enum details.

Put together, that looks something like...

    use snafu::prelude::*;
    use std::{
        fs,
        path::{Path, PathBuf},
    };
    
    #[derive(Debug, Snafu)]
    enum ErrorImpl {
        #[snafu(display("Could not read the config file {}", path.display()))]
        UnableToReadConfig {
            source: std::io::Error,
            path: PathBuf,
        },
    
        #[snafu(display("Could not write the config file {}", path.display()))]
        UnableToWriteConfig {
            source: std::io::Error,
            path: PathBuf,
        },
    }
    
    #[derive(Debug, Snafu)]
    pub struct Error(ErrorImpl);
    
    pub type Result<T, E = Error> = std::result::Result<T, E>;
    
    pub fn do_stuff_with_config(path: &Path) -> Result<()> {
        let config = fs::read_to_string(path).context(UnableToReadConfigSnafu { path })?;
        fs::write(path, config).context(UnableToWriteConfigSnafu { path })?;
        Ok(())
    }
Other things about SNAFU:

- It's very easy to add valuable context to the errors. See how the `&Path` context is transformed to a `PathBuf` with low ceremony in the example.

- You can create struct- or enum-based errors.

- You can use "stringly-typed" errors (akin to anyhow) but in combination with strongly-typed errors. This allows you to start out with a loose error handling regimen and make it stronger as you go along.

- There's support for capturing backtraces or lightweight file/line/column information.

- There's a pretty error reporter for usage with `main` functions or tests.

- There's support for the nightly-only Provider API.

OK, but what do you do then? Its not really helpful to say "this bad", if you don't offer a "this good". Of the maybe 10 approaches I have seen to Rust error handling (including using external crates, gross), the "enum idiom" is the most elegant and flexible to me, and coming from another language feels the most natural.