4 ms·
My main gripe with Rust so far has been the unnecessary profusion of Result<> types, making it hard to process and forward errors. Case in point: the example i
by Inufu 9y ago
My main gripe with Rust so far has been the unnecessary profusion of Result<> types, making it hard to process and forward errors.
Case in point: the example in the article from the rust documentation that converts errors to strings just to forward them: https://doc.rust-lang.org/book/error-handling.html#the-limits-of-combinators https://doc.rust-lang.org/book/error-handling.html#the-limit...
In practice, I find a type like Google's util::StatusOr (https://github.com/google/lmctfy/blob/master/util/task/statusor.h https://github.com/google/lmctfy/blob/master/util/task/statu...) a lot easier to use (I've written >100kloc c++ using it). This uses a standardized set of error codes and a freeform string to indicate errors. I've yet to encounter a case where these ~15 codes were insufficient: https://github.com/google/lmctfy/blob/master/util/task/codes.proto https://github.com/google/lmctfy/blob/master/util/task/codes...
- pornel 9y agoWith the addition of `?` operator I think it's no longer the problem. It keeps error handling explicit, but the syntax is small enough that it doesn't make code noisy or tedious to write. Note that you can also make your functions return `Box<Error>` which works like a base class for all common errors, so you don't have to worry about converting error types.
- Inufu 9y agoSure, but if you want to actually check for some error condition (say distinguish between file opened ok, file not found, or some other error) - which is the whole point of having an error type to begin with, otherwise you could just use optional - you still have to look up the definition of the actual error type used every time. A standardized error type used by everything removes that need - I know I can just call util::IsNotFoundError(..), no matter which library I'm using.
- kancer 9y agoYou may be interested in something like https://github.com/tailhook/quick-error https://github.com/tailhook/quick-error. It lets you easily implement From traits for errors so that you can convert between error types.
- Matthias247 9y agoI think having Result alone does not make error handling complicated, but having different error types for each operation (and concrete result) instead of using one generic error type for all of them does by pushing the job of unifying erros towards the user. Go works around the problem by Error being an interface, which means any function can return any kind of error without needing to transform it to another form. However Go benefits from the Garbage Collector here - I totally understand why Rust libraries don't want to return heap allocated errors. Maybe C++ std::error_code/error_condition provides some kind of middle ground: It should not require a dynamic allocation. And yet the framework can be expanded: Different libraries can create their own error codes (categories), and error_codes from different libraries can all be handled in the same way: No need for a function that handles multiple error sources to convert the error_codes into another type. The downside is that the size of the error structure is really fixed and there's no space to add custom error fields to it for error conditions that might require it. A custom error type in Result<ResultType,ErrorType> can be as big or small as one needs.
- deleted 9y ago[deleted]
- whyever 9y ago> instead of using one generic error type for all of them But you can do that in Rust, just use Box<Error>. No library does that though, because it is not a zero-cost abstraction.
- leshow 9y agoNote that Rust's Error is also a trait (similar to an interface) and Box<Error> does return heap allocated errors, and you're free to use that if you like. Being a trait object, it will erase the type of the error, but you don't have to write any of the accompanying From or Error implementations you normally would with your own ErrorType.
- pcwalton 9y ago> However Go benefits from the Garbage Collector here - I totally understand why Rust libraries don't want to return heap allocated errors. This doesn't have to do with the GC or lack thereof. Instead it's part of the philosophy of zero-cost abstractions: idiomatic C libraries don't require heap allocations to return errors, so neither does Rust.
- bascule 9y ago> Case in point: the example in the article from the rust documentation that converts errors to strings just to forward them This section: - Shows you how to define your own Result types. They have chosen a String as an example of what you could use as an error type. In practice nobody uses "String" as an error type. - Concludes by defining a custom error type to use instead of a String. I guess you didn't read that far? In practice nobody "converts errors to strings just to forward them". String was just an example they were using as they built up to defining a custom error type. Rust errors can be forwarded as simply as "?". The conversions can be handled automatically with "From" traits. The "error-chain" crate takes care of these conversions for you, wrapping the original errors so they're still available (including stack traces), but aggregating them under a set of error types specific to your crate: https://github.com/brson/error-chain https://github.com/brson/error-chain
- pm215 9y agoUsing String as an example error type seems like a bad choice if nobody actually uses it in practice, though -- it's just leading you down the garden path. Personally I found the error handling section of the documentation confusing and frustrating -- it works through three or four different approaches pointing out issues with them as it goes, and it's hard to tell when it's discussing a simple-but-wrong approach as motivation for the following more-complex-but-correct one, and when it's actually recommending you use the approach. Plus it finishes with an approach with nice properties but an awful lot of boiler plate conversion code, which left me thinking 'surely there must be a better way'. IMHO the error handling section of the rust docs should describe just one way to do things, and it should be the standard way everything uses so your code interoperates with library errors nicely, and that way should not require writing a page of boilerplate just to say 'my function might return an error from library foo or one from library bar or this error of its own'. (If error-chain is that one right way then it should be in the standard library and the documentation.) As it is it looks like 'this language isn't finished yet, come back in six months to see if it's any better' :-(
- burntsushi 9y agoThe reason why I wrote it that way was to motivate why error handling is the way it is. I personally think it's hard to just throw the "right answer" at someone and hope they get it, because error handling isn't some rote process you can just plow through. It's important to understand the case analysis involved so that you can choose the right granularity of error handling for your task. All of the error handling strategies in that chapter are interoperable to some degree (with perhaps "panic on error" being the odd duck out). They aren't incompatible philosophies. With that said, thank you for the feedback. When I circle back around to it, I'll make sure to put more emphasis on The Right Way. The conclusion already has some of it, and the case study is supposed to show the progression in action, but perhaps more is needed. I will let others focus on more targeted advice, since one huge chapter on error handling is only part of the story. The purpose of the error handling chapter is start with someone who might not even know what `Option<T>` is, and take them all the way through `try!`, the `Error` trait and automatic `From` conversions from first principles. More than that, it's supposed to teach you why using `String` or `Box<Error>` for your error type can be bad, even if it is ludicrously convenient. Rust is a young language. I expect error handling idioms to evolve. Evolution doesn't mean something isn't ready to be used, because all languages evolve in some way.
- Const-me 9y ago> I've yet to encounter a case where these ~15 codes were insufficient Insufficient on Windows. There’re thousands error codes you can get from any Windows-provided API. You can pack each of them into a single int32 value (using HRESULT_FROM_WIN32 macro for old-style error codes, the newer APIs already return HRESULT), but still, significantly more than 15.
- jsolson 9y agoGoogle's util::Status and util::StatusOr are actually quite flexible. While the generic error space is recommended for most work (and really, rather a lot fits in that space), it does support the notion of other error spaces like POSIX or Windows. At Google† I do quite a bit of interacting with the kernel, so my code makes fairly heavy use of the POSIX space. That said, in the vast majority of cases any error I might be reporting from the POSIX space can be just as if not more usefully expressed (for the consuming software) using one of those ~15 generic codes. If their semantics are properly adhered to, those codes give good guidance on when an operation is guaranteed to have failed (but can be retried), when it's guaranteed to have failed (but cannot be retried without changing the request), when its fate is unknown, and when it has succeeded. In many cases this allows for generic error handling policies that fit a given application well. With enormous error spaces that is much more challenging. In the cases where the underlying error deliveries clear value and I'm communicating across an abstraction boundary (I find the intersection of these is relatively rare), the Status type supports (albeit somewhat awkwardly) nesting. That allows the basic error to be one of the canonical types and the precise error to be communicated as a nested Status. † I work on Google Compute Engine's on-host network devices and dataplane.
- Const-me 9y agoI mostly work on desktop and mobile software. “Unable to open the file: access denied” is helpful for end-user, will cause them to go fix filesystem permissions on the file they are trying to open, or restart the app elevated. “Unable to open the file: failed precondition” translates to “this software is broken, we don’t know why” > With enormous error spaces that is much more challenging. Not sure I understand the problem. On Windows, APIs are typically designed as reliable (this applies to both OS API, and the way third-party developers design stuff). If something is failed but the condition is temporary and might have fixed with retry, well-designed API will retry itself, possibly accepting timeout argument. That’s why you can do generic error handling just fine: FAILED() macro is enough for 99% cases.