6 ms·
I encourage people to check out my SNAFU crate [1]. I urge users to create many distinct error types (usually enums but also structs) and compose them — so much
by shepmaster 3y ago
I encourage people to check out my SNAFU crate [1]. I urge users to create many distinct error types (usually enums but also structs) and compose them — so much so that I advocate that you never create each specific error in more than one source location. That means that the collection of error types produces a unique trace through your program, uniquely identifying the source of the error with no runtime cost (compare this to a runtime-collected stacktrace / backtrace).
Here's how the final example in the blog post would look like with a quick transform to SNAFU: https://gist.github.com/shepmaster/fb7f4c9519a074ea7186ca7b75afb9dd https://gist.github.com/shepmaster/fb7f4c9519a074ea7186ca7b7.... If I spent more time, there'd probably be a bit more changes, but hopefully that gets the idea across.
[1]: https://docs.rs/snafu/ https://docs.rs/snafu/
- remram 3y agoWhat for? What do you expect the user of your library to do with this detailed information?
- shakow 3y agoBetter understand what happened, and if/how you can fix it.
- remram 3y agoI'm not saying why have error reporting at all, I'm asking about this specific approach, for example as opposed to the article being discussed.
- shepmaster 3y ago> as opposed to the article being discussed. I'd actually say that the article and my suggestion are basically compatible. If you check out my gist [1], you'll see that I have basically the same number of user-facing types (or maybe a slightly smaller number as I merged a few structs and enums in some spots). [1]: https://gist.github.com/shepmaster/fb7f4c9519a074ea7186ca7b75afb9dd https://gist.github.com/shepmaster/fb7f4c9519a074ea7186ca7b7...
- epistasis 3y agoI would expect that the primary use is for those debugging the library. Though this could be users, it would also be developers of the library. Though, even as a library user, knowing the source of an error can be useful for working around the issue, temporarily, or maybe even figuring out that I was using the library incorrectly.
- shepmaster 3y agoThat's an interesting question that I find difficult to answer. To me, it's as if you are asking "why provide any detail about what happened?". The nonsense end result of that line of logic is that you return a single boolean that corresponds to failure/success, so I assume that's not what you mean. Contrast the two error messages: > one end of range is not a valid hexadecimal integer > the beginning of the range ("0xZZ") is not a valid hexadecimal integer The latter has more information and points the user to the general area of the problem, and probably allows them to fix the problem themselves. It could be even more improved by providing line/column information. By splitting out more and more error types (and cases), and artificially limiting yourself to using each error once, you push yourself away from "range is bad" style errors and more specific (and ideally descriptive) errors.
- remram 3y agoI'm not saying you shouldn't expose it, but why expose it as distinct error types? Why expose it as a programmatically-accessible information at all? I can see how the developer debugging the library might want it, but what is the advantage of this information being a type/enum/... over the same information in a string, if the only purpose is for a developer to read it?
- orf 3y agoIt fits better with the language. It doesn’t cost much (anything?) and in simple cases (errors with no dependent fields) returning it is akin to returning an error code.
- shepmaster 3y ago> Why expose it I read "expose" in two possible ways: (1) as a public- / user-facing API (2) existing at all. When an error type is a public API that is bound by semver, I do think you should be very careful about what you expose. I suggest starting with an opaque [1] error type and only exposing exactly what you are comfortable with supporting. That may boil down to basically only a string (a.k.a. the `Error` trait). > Why expose it as a programmatically-accessible information at all? A few things come to mind... I'm a huge fan in testing my error cases as much as possible. To that end, a bunch of my tests are semantically `assert_matches!(Err(MyError::Case { .. }), function_result_value)`. Not all errors are equivalent. For example, if my library fails to read a configuration file, perhaps the caller of the library can recover from that by downloading a file. However, this requires the caller to be able to tell what caused the error. Interacting with an external API, such as HTTP status or command line exit codes. In this case, you can categorize your errors into domains like "server error" / "client error" / "authorization error". These could all be done by string matching, but that tends to be comparatively brittle. > the same information in a string Aggregating strings like that requires dynamic allocation, which isn't universally available in Rust programs. For example, SNAFU works fine in a `no_std` environment and I know that it's been used in cases like embedded and Windows kernel drivers. > if the only purpose is for a developer to read it I don't think that's always true. [1]: https://docs.rs/snafu/latest/snafu/guide/opaque/index.html https://docs.rs/snafu/latest/snafu/guide/opaque/index.html