4 ms·
That'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
by shepmaster 3y ago
That'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
- MaulingMonkey 3y ago> 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. I frequently find that is all that is necessary, and is exactly the correct solution, not "nonsense" whatsoever. Perhaps even that boolean is overkill: an unrecoverable bug should perhaps instead log and then terminate, producing no error condition for the caller of your library to worry about attempting to recover from whatsoever. Detailed error reporting can be done without obscenely distinct error types, and in fact detailed error reporting frequently benefits from a focus on the task itself instead of trying to structure data to leave the task of actually reporting the error to someone else, and kicking the can down the road. E.g. for parsing focused errors like this I'd be more interested in pretty printing the source line in question, underlining the error, etc. - I wrote https://github.com/MaulingMonkey/json-spanned-value https://github.com/MaulingMonkey/json-spanned-value for helping ease the display of bad JSON data in a manner that integrates with your IDE for ease of fixing said data by jumping directly to the cause. And then, admittedly, there are times when different errors should be recovered from differently. When the caller might wish to retry an operation after some errors, try a different operation after other, report file+line+range information for yet other errors, etc. - these have very concrete answers to "What do you expect the user of your library to do with this detailed information?" that are not difficult to answer at all. > Contrast the two error messages: Both are UTF8 strings without types adding anything obviously useful. A silly demo screenshot of errors that will open the offending document if closed, and navigate to the line/column of said error: https://github.com/MaulingMonkey/json-spanned-value/blob/master/examples/demo.png https://github.com/MaulingMonkey/json-spanned-value/blob/mas... Which operates by immediately dumping the "errors" to terminal without any error types involved whatsoever... nor even a boolean branch! Well, a real program might set a boolean so the CLI knows to exit(1) instead of using partially parsed data...
- jsmith45 3y ago> an unrecoverable bug should perhaps instead log and then terminate For an application, sure that can be fine. One needs to be careful in a library, because it is not always the case that what seems like an unrecoverable error to the library author is always unrecoverable for the application. Consider a library designed to retrieve process information from "/proc". I could very easily see a library author concluding that if "/proc" does not exists that is an unrecoverable error worthy of termination. After all, in that case, the library is useless. The application that wants to use the library may strongly disagree, and may be expecting that /proc might be unavailable in some locked down environment in which it sometimes runs, and has some fallback it can use in those environments. If you return a error the application can easily fallback. Otherwise you are forcing the application to do something like check for /proc existing before even calling your library. A library with global state may assume that if some invariant of that global state fails to hold (and this got detected) that this should be treated as unrecoverable. And well this one can also vary. Sometimes having the process die here can be the right approach, especially for certain development scenarios, since this means there is either a code bug, nasty undefined behavior, or possibly a compiler bug going on. But depending on the nature of the library it might be possible that there is a sensible way to reset things without bad side effects, in which case, it could potentially make sense to return an error indication that the application could potentially handle by unwinding to a state where resetting your library is sensible, and then resetting it.