-
Notifications
You must be signed in to change notification settings - Fork 18
Error Handling FAQ #50
Comments
I'd like to leave one comment and one proposed question. CommentI suggest that "index out of bounds" (and "trying to access a location beyond the end of an array" in rust-lang/rust#95660) is an unfortunate example to use for an unrecoverable error that justifies panicking, because many types provide I'm not sure what would be a good example of an unrecoverable error, but maybe apparent memory corruption (making it dangerous to try to recover from within the same process) or reaching "unreachable" code (but then an example of "unreachable" code is needed). I suppose (without good data) that the most common 'legitimate' use of panicking is for otherwise-recoverable errors in functions of which the return types are constrained by some trait or other API not to be able to return error values. Proposed question"There are many different error handling libraries in use, such as While I think it makes more sense to leave it up to individual library authors to explain their targetted use-cases and maybe how their libraries differ from others', I suggest it could be useful for a central resource
|
Sounds reasonable! I'd be in favor of further updates to pick a better example. I copied the index out of bounds example straight from the book, so if we're going to update that in the
I'm very hesitant to make concrete suggestions but I agree this is something we should address. I had a plan a while back to improve and reorganize the error handling library section in |
I have never found this useful as the relevant distinction. In my experience the relevant distinction has been that panicks are for signalling that a bug in the program has been detected, and errors are for anticipated runtime failure modes in a correct program. Whether any of these is recoverable or unrecoverable is quite a separate thing. For example a bug in the program might be recoverable at a thread boundary or HTTP server request boundary, but not if not called from one of those contexts. The unrecoverable vs recoverable distinction ends up being circular and unhelpful. Like @8573 mentioned about indexing, imagine someone deciding whether to index using In contrast it makes sense to look at whether something is a bug or not. For slice indexing, there are places where a specific indexing operation being out of bounds could only occur if the program is buggy, and other places where indexing might be out of bounds because the program is correct but a user-provided config file contained something silly. This is a useful distinction to inform someone on whether they should write |
I have a question that falls under the "How should I structure my error types?" category. When designing an Error, should I make one big crate-wide error? should it target a module? or should it be specific to a function, etc? Looking at #11, I spent some time thinking about Doubling back to my original question, if we could turn back time, would it be "better design practice" to split |
To me this feels like a problem with not having shared definitions, but I'm fine with standardizing on "anticipated" vs "unanticipated" instead of "recoverable" vs "non-recoverable". I can see the former being more direct where as the later implies the former in my mind. I don't like focusing on "bug" as much but I don't have a strong justification for this as much as a gut feeling that it's not going to cover all the cases where you'd want to panic, but the only thing that comes to mind is Overall I guess what I mean to say is 👍 good feedback tyty Edit: I've gone ahead and updated the FAQ answer in the top level comment. I'll open a PR to update the docs on nightly tomorrow. Not sure when/if I'll get around to updating the book, maybe once I've gotten more positive feedback on this new framing. |
So I anticipate this (structuring errors, not the io::Error stuff in particular) being the single biggest question we need to answer and my current plan is to approach this as methodically as possible. I'm planning on conducting a series of case studies to analyze the various approaches people take and get a solid understanding of the pros and cons and when each error design approach (e.g. fine grained vs flat) works best. I already have a few potential candidates for case studies written down but doing a |
This framing is very different from what I had in mind. Anticipated vs unanticipated isn't a distinction I intended to make in #50 (comment). In reality they are both anticipated. Generally something you didn't anticipate, you didn't write code for, so whether you wrote a panic or an error in the code that you didn't write isn't a question: you wrote neither. Buggy program vs correct program is the point. When you write an assertion to confirm a precondition, that is an anticipated precondition violation in your buggy caller that you are aiming to catch. All the unintended-to-be-reachable |
In my mind "unanticipated" means "you anticipated that this condition would never occur or should never occur", this ties into why I think what I really want is a word that really mirrors |
unexpected ? |
"would never occur" — I think this inevitably becomes about probability, which I would object to. Anticipating that something would not occur means that you feel it is highly unlikely to occur. Yet, some kinds of buggy programs (panicks) arise more commonly than certain niche runtime failure modes in correct programs. For example at least tens of thousands of people have written Rust code with unintended integer overflows, while 3-4 orders of magnitude fewer people have ever encountered a "should never occur" — This is a statement about whether a program is buggy or not. If a program does something that it should not do, i.e. was not supposed to do, we call that a bug. To me it isn't helpful to wrap this behind a word like unanticipated or unexpected, when the bugginess is the essence. Precondition assertions are a good example of why. It's correct to write a panicking assertion even with the full expectation that some fraction of our callers will screw it up and trip the assertion, and will need to fix their code to call us correctly. |
I very strongly agree with @dtolnay here about the recoverable vs unrecoverable thing. I've also never found that distinction to be particularly helpful. The way I've always thought about it is this: if a panic occurs, then there must be a bug. Sometimes it might be appropriate to convert the panic to an error, but it's probably just as likely (if not more so) that the panic indicates some problem with a piece of internal logic somewhere or a case that wasn't handled. Whether you can "recover" from it or not seems like an orthogonal issue. |
Alright, I updated it to be focused on buggy vs correct programs, let me know what you think of the wording. |
"Detected memory corruption" is not a good example here. I have never seen panicks intentionally used for that. Once you are corrupting memory, that's nearly always UB territory and whether the program panicks or not is no longer something you control. I think either of my examples from my previous comment would be more illustrative: precondition violation or overflowing integer arithmetic. Separately, the "should never occur" part of this I think is not adding value. I think leaving that out would make a better juxtaposition between detecting buggy program vs failure mode in correct program. We are not making a judgement that Result errors are ones that "should occur" in any sense. If it sounds like I'll keep having objections until you use exactly the wording from the top of #50 (comment), that's probably correct, since I have been using that framing consistently for at least 2 years (like dtolnay/anyhow#81 (comment)) and it's been the result of a couple years of iteration before that. (Obviously I could still be wrong though, even if my opinion isn't going to change.) |
There are two reasons I'm having trouble copying your wording verbatim. First, I want to make it clear that panics are a type of error. Second, I want to focus the answer to this question on the interfaces that caused the confusion originally, The first issue is important to me largely because of my error in core work and the unification of The second one is important because I want to focus on the specific interfaces that caused the confusion that inspired this specific frequently asked question. I want to describe the specific roles of the I'm struggling a bit with succinctly expressing the differences between these two interfaces. The problem being, in my mind Given that, how do you feel about these attempted rephrasings based on your original framing:
|
Two points, if I may:
In short: if a programmer uses a panic, that just means the program does not have a way to handle the error. Whether that is the right choice, and whether that is an actual bug or not if it happens, and whether abort or unwind should be used, etc., it all depends on the requirements. |
I think this very succinctly touches on some of my discomfort around framing this entirely in terms of correctness vs bugs. That's why I've historically leaned on arbitrary jargon, but clearly that requires shared understanding of definitions that is impossible to achieve universally. |
Ah, yes, I didn't mean for the Project Group to endorse particular libraries over others. Of the categorizations I suggested, I wouldn't see any as judgemental (but maybe others would?) except "Obsolete", and that I intended only for libraries that clearly are considered obsolete by their own maintainers (such as by being archived on GitHub in the cases of |
I think both of #50 (comment) are an extreme stretch beyond what it makes sense for this document to take into account.
This is a Russell's paradox / Richard's paradox-level loophole. If someone writes software to detect bugs (such as Clippy), does it become impossible for them to test their software, because "write a program that contains a bug" is a requirement that is definitionally impossible to accomplish? The fix is the same as for all the math paradoxes. Define a logic in a way that does not make reference to the truth or falsity of statements in that logic; define the requirements of a program in a way that does not make reference to that program's own bugginess. Testing test frameworks, where your program is only right if it's wrong, is not a useful scenario to teach someone about error handling.
This one is even more of a stretch. If we subscribe to this, every standard library function would need a caveat that says "except if a cosmic ray makes it do something different this time". |
@yaahc, #50 (comment) looks terrific to me. |
reword panic vs result section to remove recoverable vs unrecoverable framing Based on feedback from the Error Handling FAQ: rust-lang/project-error-handling#50 (comment) r? `@dtolnay`
Another question that came up today: What wording should I use in
|
Personally, I would always use expect for precondition that should be true no matter what user do (minus some good reason). An the matter of expect subject, I answered a SO question Is there a more concise way to format .expect() message?. The |
reword panic vs result section to remove recoverable vs unrecoverable framing Based on feedback from the Error Handling FAQ: rust-lang/project-error-handling#50 (comment) r? ``@dtolnay``
They are not an "extreme stretch", not even for pedagogical matters.
A tool like Clippy may have tests that contain a pattern that is considered undesirable or a bug in normal programs -- that is just fine, no loophole there. If you do not like that counterexample, there are many others. Some even arise in common situations. For instance, anybody writing "scripts" for themselves may decide to use
To me, it sounds like you are assuming panics are "wrong". That is the root of the problem. Panics are not wrong. In fact, they are the best solution in some cases.
No, the standard library is not claiming to guarantee anything outside Rust semantics. And, to be clear, my points are not about writing those precise examples in the documentation, but to avoid identifying "correctness" with "no panics". |
reword panic vs result section to remove recoverable vs unrecoverable framing Based on feedback from the Error Handling FAQ: rust-lang/project-error-handling#50 (comment) r? ```@dtolnay```
@ojeda touches on something I was thinking about. If |
I would imagine that the developer typing |
@ojeda am I interpreting your concern correctly if make it so we talk about it in terms of "panic should be used for errors that represent bugs, result should be used for errors that represent anticipated runtime failure modes" but leave correctness out of it and hopefully imply that should doesn't mean must? |
Or, maybe the developer doesn’t know how to fix the problem if the program blows up at a certain point.
CEO’Riley
… On Apr 8, 2022, at 21:00, 8573 ***@***.***> wrote:
If panic! indicates the presence of a bug, then wouldn't developers just fix the bug and choose not get themselves in a situation where they are typing panic!?
I would imagine that the developer typing panic! commonly doesn't know where a bug might be that would violate their assumption and trigger the panic — elsewhere in their crate, in a dependency, in a dependent, or maybe nowhere.
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you are subscribed to this thread.
|
reword panic vs result section to remove recoverable vs unrecoverable framing Based on feedback from the Error Handling FAQ: rust-lang/project-error-handling#50 (comment) r? ````@dtolnay````
Yeah, I think that would be better. An alternative could be to introduce panics without a mention to bugs right away, but as a way to diverge abnormally. They may be used for different purposes, and reaching them may be a bug in the code itself (e.g. reaching an unexpected logic state, the unanticipated failure of an operation, a precondition violation, etc.) or not (e.g. misuse of a program within a bigger system, a situation that was not required to be handled, testing purposes, documentation examples, soft errors and hardware faults in real life executions, compiler bugs, etc.). |
I've gone ahead and created an update to |
Add section on common message styles for Result::expect Based on a question from rust-lang/project-error-handling#50 (comment) ~~One thing I haven't decided on yet, should I duplicate this section on `Option::expect`, link to this section, or move it somewhere else and link to that location from both docs?~~: I ended up moving the section to `std::error` and referencing it from both `Result::expect` and `Option::expect`'s docs. I think this section, when combined with the similar update I made on [`std::panic!`](https://doc.rust-lang.org/nightly/std/macro.panic.html#when-to-use-panic-vs-result) implies that we should possibly more aggressively encourage and support the "expect as precondition" style described in this section. The consensus among the libs team seems to be that panic should be used for bugs, not expected potential failure modes. The "expect as error message" style seems to align better with the panic for unrecoverable errors style where they're seen as normal errors where the only difference is a desire to kill the current execution unit (aka erlang style error handling). I'm wondering if we should be providing a panic hook similar to `human-panic` or more strongly recommending the "expect as precondition" style of expect message.
All expect messages are adjusted or replaced with unwrap following the guidelines here: rust-lang/project-error-handling#50 rust-lang/rust#96033
All expect messages are adjusted or replaced with unwrap following the guidelines here: rust-lang/project-error-handling#50 rust-lang/rust#96033
All expect messages are adjusted or replaced with unwrap following the guidelines here: rust-lang/project-error-handling#50 rust-lang/rust#96033
reword panic vs result section to remove recoverable vs unrecoverable framing Based on feedback from the Error Handling FAQ: rust-lang/project-error-handling#50 (comment) r? ````@dtolnay````
Add section on common message styles for Result::expect Based on a question from rust-lang/project-error-handling#50 (comment) ~~One thing I haven't decided on yet, should I duplicate this section on `Option::expect`, link to this section, or move it somewhere else and link to that location from both docs?~~: I ended up moving the section to `std::error` and referencing it from both `Result::expect` and `Option::expect`'s docs. I think this section, when combined with the similar update I made on [`std::panic!`](https://doc.rust-lang.org/nightly/std/macro.panic.html#when-to-use-panic-vs-result) implies that we should possibly more aggressively encourage and support the "expect as precondition" style described in this section. The consensus among the libs team seems to be that panic should be used for bugs, not expected potential failure modes. The "expect as error message" style seems to align better with the panic for unrecoverable errors style where they're seen as normal errors where the only difference is a desire to kill the current execution unit (aka erlang style error handling). I'm wondering if we should be providing a panic hook similar to `human-panic` or more strongly recommending the "expect as precondition" style of expect message.
I hope this is the right place to ask. This is a common error in parsers and it does not fit the Error trait well. |
That usecase is one of the motivations behind the design of the provider API: rust-lang/rust#96024 Here's the companion generic member access RFC that I still need to update and actually get merged1: rust-lang/rfcs#2895, or more specifically the third bullet of this subsection: https://github.com/yaahc/rfcs/blob/master/text/0000-dyn-error-generic-member-access.md#example-use-cases-this-enables
It doesn't really make it easy, nor does it actually enforce any standard way of accessing the chain of errors that exist at the same level, you could in theory have one library that provides their vec of sources as an iterator type and another that provides them as a slice, and you have to know what types to request which causes some potential footguns. However, it does make it possible to pass this information out across a There are some other patterns I can recommend depending on the specific use case, and whether or not you need to extract the vec of errors thru a Footnotes |
Thanks a lot, I think I understand the RFC better! My current understanding(Please let me know if I got it all wrong!) Provider/Demand RFC rust-lang/rust#96024The
With this, any type implementing the The companion RFC rust-lang/rfcs#2895At first sight, I thought that it is just the "convenience" methods after "errors implement the
Back to returning multiple errorsFor my case, it seems that the following holds.
Questions
|
Reducing confusion around error handling in the rust-lang ecosystem is one of the top priorities for the error handling project group this year. As part of that effort we're going to start maintaining a list of Frequently Asked Questions about error handling.
For now this issue will serve as a living document for these FAQs, though eventually I hope to move them to a move permanent / visible location.
Unanswered Questions
If you have any questions that aren't covered by this list, or if you feel any of the answers here fail to completely answer their question for you please let us know so we can improve this list. You can follow up the following ways:
Frequently Asked Questions
When should I use
panic!
vsResult
?panic!
macro to construct errors which signal that bug has been detected in the program, useResult::Err
to wrap error types that represent anticipated runtime failure modes in a correct program.panic!
macro: https://doc.rust-lang.org/nightly/std/macro.panic.html#when-to-use-panic-vs-resultProposed Questions
How should I structure my error types?
TODO (complex and situational)
My current plan is to do a series of case studies on user codebases in various domains. If you have a codebase that you think would be a good candidate for a case study, either because you feel it has optimal error handling that should be used as an ideal example or because it needs improvement, please reach out so I can add you to the list of potential candidates.
There are many different error handling libraries in use, such as quick-error, error-chain, failure, snafu, thiserror, anyhow, and eyre. [I'm just listing those in the order in which I think they were released.] How do they differ? What different use-cases do they target?
TODO
crates.io
andlib.rs
for further looking up available error handling libraries.The text was updated successfully, but these errors were encountered: