Open yaahc opened 2 years ago
I'd like to leave one comment and one proposed question.
I suggest that "index out of bounds" (and "trying to access a location beyond the end of an array" in https://github.com/rust-lang/rust/pull/95660) is an unfortunate example to use for an unrecoverable error that justifies panicking, because many types provide get
methods that do handle that case recoverably, allowing combining checking whether an index is valid and indexing by that index into a single method call, supporting the idiomatic pattern-matching style if let Some(x) = v.get(i) { … }
over the C-like if i < v.len() { let x = v[i]; … }
.
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.
"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?"
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
error-chain
and failure
).Comment
<...>
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 panic!
docs we may want to also update it in the book itself.
Proposed question
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 awesome-rust
to basically have a bunch of tags like "error defining" or "error reporting" which we could then apply to each item in the list. I'd be happy to go ahead and make that change now, link it here as part of that question, and generally say "it depends on what you're doing, here is the categorization of needs that we think is important, here's a community maintained list of those categorizations that you can add your libraries too that also contains further resources related to each library" and see if that is helpful enough.
Use
panic!
for unrecoverable errorsuse
Result
for recoverable errors
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 [
or get
in a particular spot. If they use get
, it's gonna be recoverable because they can match on the result. If they use [
, it's gonna panic and they can't recover it, because the program will either abort or unwind and catch_unwind
's documentation says it's not supposed to be used for try/catch. So we've basically told them "if you use get
then use get
; if you use [
then use [
".
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 [
or get
.
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 https://github.com/rust-lang/project-error-handling/issues/11, I spent some time thinking about std::io::Error
. It's a pretty chonky error. I wonder how, for any given function in std
that returns an io::Error
, you are supposed to reason about which ErrorKind
might be returned. I suppose most people let a variant bite them (preferably near a hospital) first or dig into the source code. Maybe for most users, std::io::Error
s are "unrecoverable" so they just log and die/fail operation, but the std
library is not in a position to assume context, so it favors recoverability.
Doubling back to my original question, if we could turn back time, would it be "better design practice" to split std::io::Error
into a hypothetical std::net::Error
, std::fs::Error
, ect. Should it be even more granular?
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.
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 unimplemented!
/todo!
, which fit very well into the "unanticipated" framing but not as well within a "bug" framing. I agree with your point on the unrecoverable vs recoverable dichotomy not meshing well with the catch_unwind
API we provide, and I think this anticipated vs unanticipated dichotomy that you've suggested fits it better.
Overall I guess what I mean to say is :+1: 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.
Doubling back to my original question, if we could turn back time, would it be "better design practice" to split
std::io::Error
into a hypotheticalstd::net::Error
,std::fs::Error
, ect. Should it be even more granular?
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 std::io::Error
redesign as a crate or fork of rustc and getting some codebases to trial using it sounds like a very interesting case study, but beyond that I don't feel comfortable making assertions at the moment with regards to this specific question.
I'm fine with standardizing on "anticipated" vs "unanticipated". […] I don't like focusing on "bug" as much
Use
panic!
for unanticipated errors which indicate a bug in the program has been detecteduse
Result
for anticipated errors that can reasonably be reacted to
This framing is very different from what I had in mind. Anticipated vs unanticipated isn't a distinction I intended to make in https://github.com/rust-lang/project-error-handling/issues/50#issuecomment-1090876982. 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 unwrap
s you find in stuff like BTree internals are about anticipating bugs. (And regarding the unimplemented!/todo! panicks, I would consider incomplete programs to be a subset of buggy programs rather than correct programs, but maybe there is a different word than buggy that captures this better.)
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.
In my mind "unanticipated" means "you anticipated that this condition would never occur or should never occur", this ties into why expect
is named the way it is, so you can put a description for why you expect it to be okay to panic. I still think I prefer the framing of unanticipated vs anticipated, but I'm open to any framing that further reduces ambiguity and confusion. I think we both agree on when panic is appropriate, it's just the words we are using to describe them that is taking some bikeshedding.
I think what I really want is a word that really mirrors expect
, some concise way to say "something we expect to not happen". Bug seems reasonably close approximation of that but I am not thrilled with it.
unexpected ?
In my mind "unanticipated" means "you anticipated that this condition would never occur or should never occur"
"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 FilesystemLoop
IO error kind. Anticipating that it would never happen is not why we made integer overflows panic. Meanwhile nearly 100% of people would be correct estimating that they'll never see a FilesystemLoop
error in their lifetime.
"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.
Use
panic!
for errors that should never occur and indicate a bug in the program has been detected (e.g. detected memory corruption or reaching anunimplemented!
code path)use Result for errors that represent anticipated runtime failure modes in a correct program (e.g. file not found)
"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 https://github.com/rust-lang/project-error-handling/issues/50#issuecomment-1090876982, that's probably correct, since I have been using that framing consistently for at least 2 years (like https://github.com/dtolnay/anyhow/issues/81#issuecomment-609247231) 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, panic!
and Result
, not the concepts they represent, aka panicking vs errors.
The first issue is important to me largely because of my error in core work and the unification of &dyn Error
and panic!
that I talked about in https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html. I think of unwrap
and expect
as constructors for errors that represent bugs where the source error is a different error type (E
) that represents an anticipated runtime failure. I want to make it so the panic handler can access the &dyn Error
as a source when printing errors from unwraps
and expects
and utilize the full expressive power of the error trait interface when reporting these errors, rather than lossily passing it through Debug
. To me this is a fundamental missing operation in rust today, and is very much related to the comment from anyhow that you linked above. The poison error is a source error for bugs 99% of the time. We should have an interface for smoothly transitioning from possibly an anticipated runtime failure to a definite bug. I'm rewording your statement to make it clear when describing both of these interfaces that they represent errors, and attach meaning to the error (signalling a bug vs an anticipated failure mode).
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 panic!
and Result
interfaces, how they relate, and how they differ. The issue to me isn't the "should not occur part" but the "panicks are for signalling, ... errors are for ..." wording in your answer, which is forcing me to try to reword it to talk about the interfaces themselves and I end up adding other bits to make it flow which you end up objecting to.
I'm struggling a bit with succinctly expressing the differences between these two interfaces. The problem being, in my mind panic!
is a constructor for a type of error, where as Result
is a wrapper that signals that a contained type is a type of error, but it doesn't feel quite accurate to call Result
a constructor for errors in it's own right. These interfaces are not exactly analogous but it's hard to succinctly describe this while also giving people the information they need to properly use these interfaces.
Given that, how do you feel about these attempted rephrasings based on your original framing:
panic!
macro to construct errors which signal that bug has been detected in the program, use Result::Err
to wrap error types that represent anticipated runtime failure modes in a correct program".panic!
docs): "The panic!
and Result
interfaces are not completely analogous. The panic!
macro is used to construct errors that represent a bug that has been detected in your program. With panic!
you provide a message that describes the bug and the language then constructs an error with that message, reports it, and propagates it for you. Result
on the other hand is used to wrap other types that represent either the successful result of some computation, Ok(T)
, or error types that represent an anticipated runtime failure mode of that computation, Err(E)
. Result
is used alongside user defined types which represent the various anticipated runtime failure modes that the associated computation could encounter. Result
must be propagated manually, often with the the help of the ?
operator and Try
trait, and they must be reported manually, often with the help of the Error
trait.Two points, if I may:
Whether a program is correct or not all depends on the requirements. For instance, a program like fn main() { panic!(); }
may be correct if the requirement is that it always panics. For instance, for testing a testing framework.
Even if "normal" programs are expected to "not panic", panics can still happen in fully correct programs. For instance, a hardware error that makes an assert fail, even if triggering that assert is impossible given Rust semantics.
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.
Two points, if I may: <...>
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.
I'm very hesitant to make concrete suggestions
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 error-chain
and failure
).
I think both of https://github.com/rust-lang/project-error-handling/issues/50#issuecomment-1092070132 are an extreme stretch beyond what it makes sense for this document to take into account.
a program like
fn main() { panic!(); }
may be correct if the requirement is that it always panics. For instance, for testing a testing framework.
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.
panics can still happen in fully correct programs. For instance, a hardware error that makes an assert fail, even if triggering that assert is impossible given Rust semantics.
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, https://github.com/rust-lang/project-error-handling/issues/50#issuecomment-1092069775 looks terrific to me.
Another question that came up today:
Result::expect
/Option::expect
?This one I'm hesitant to be to prescriptive about, because I know people use a lot of different styles for constructing error messages via expect
. I am interested in seeing if there is any sort of consensus for how these should be used, or any further additions we could make to the docs that would help guide our users to confidently write expect msgs, even if that means just documenting all of the approaches and their advantages / disadvantages.
I am aware of two common styles for how people utilize word expect
messages.
I've mocked up a semi realistic example of both approaches here to compare their output:
Running these examples produces the following output
---- expect_as_precondition stdout ----
thread 'expect_as_precondition' panicked at 'user provided input will always be a string containing a valid u32: ParseIntError { kind: InvalidDigit }', src/lib.rs:18:10
---- expect_as_error_message stdout ----
thread 'expect_as_error_message' panicked at 'user provided invalid input: ParseIntError { kind: InvalidDigit }', src/lib.rs:11:10
While I am personally in the "likes to use expect to document preconditions" camp I think it is important to acknowledge that the default output of expect
is not really sympathetic to this approach, even if the name of the method itself is. The default output does nothing to acknowledge that the message was an expected precondition, and out of context it makes little sense. I think the trade off here is you end up creating a better experience for developers reviewing the .expect
calls in source but you create a worse experience for users viewing your errors.
So I'm curious:
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 unwrap
crates show that very well, I think this is a weakness of .expect()
(opposed to panic!()
feature) specially if you want to use it as user error message.
I think both of #50 (comment) are an extreme stretch beyond what it makes sense for this document to take into account.
They are not an "extreme stretch", not even for pedagogical matters.
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?
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 unwrap()
because they do not need any handling for certain cases, thus adding extra paths and complexity would not be optimal. The fact that one can trigger a panic in such a script does not mean the script is buggy. The script is correct as long as that is what was designed to do.
Testing test frameworks, where your program is only right if it's wrong, is not a useful scenario to teach someone about error handling.
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.
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".
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".
@ojeda touches on something I was thinking about. 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 think it might be valuable to suggest that panic!
is a convenience tool for signaling a bug where it is more desirable - effort wise or behaviorally - to halt the program than fix the bug. One example effort wise is scripts, where development speed is favored over robustness. Another example behaviorally is runtimes/frameworks. I suspect that the tokio
runtime entry point, block_on
, deliberately chooses to panic!
instead of return a Result
because users are not expected to be able to handle issues that may arise within tokio
's machinery.
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 typingpanic!
?
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.
@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.
@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?
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 Result::expect
's documentation based on my comment above.
I hope this is the right place to ask.
How should one deal with a collection of errors? Say Vec<Error>
?
This is a common error in parsers and it does not fit the Error trait well.
@saona-raimundo
That usecase is one of the motivations behind the design of the provider API: https://github.com/rust-lang/rust/issues/96024
Here's the companion generic member access RFC that I still need to update and actually get merged[^1]: https://github.com/rust-lang/rfcs/pull/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
error source trees instead of chains by accessing the source of an error as a slice of errors rather than as a single error, such as a set of errors caused when parsing a file TODO reword
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 dyn Error
trait boundary so long as you know the explicit types (akin to downcasting and Any).
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 dyn Error
. Happy to info dump more and help you work through possible approaches, but sadly I think the current answer to this question in Rust is that we don't have a good way to deal with that situation other than avoiding the Error
trait[^2].
[^1]: there's never enough time in the day I swear to god >_< [^2]: Actually, I don't think that counts as a good way either.
Thanks a lot, I think I understand the RFC better!
(Please let me know if I got it all wrong!)
The Provider
/Demand
API is nice by itself, even without talking about errors.
Motivated by handling errors better, there is the introduction of "errors should implement the Provider
trait".
In a different chronological order, we may have had something like the following trait bound:
trait std::error::Error: std::any::Provider
But, of course, introducing this trait bound would break a lot of code, so the blanket implementation is proposed.
// core::error
impl<E: Error + ?Sized> Provider for E;
With this, any type implementing the Error
trait also implements the Provider
API.
At first sight, I thought that it is just the "convenience" methods after "errors implement the Provider
trait".
But it is not only that.
Provide
for errors, one can not overwrite it. On the other hand, a method of the Error
trait can be specialized, namely provide_context
. dyn Error
for easy querying context easily, instead of going through a Demand
(which would be the case after only the main RFC(?)).For my case, it seems that the following holds.
Vec<MyError>
, where MyError
implements Error
and is well documented. This is okay even if Vec<MyError>
does not implement the (current) Error
trait. Vec<MyError>
should be replaced by something that implements the new Error
trait and documents that the attached context is Vec<MyError>
.provide_context
just an "alias" for the blanket implementation of provide
? What would be the difference? In other words, if the companion RFC is accepted, why would one need the main RFC to include impl<E: Error + ?Sized> Provider for E;
? Error
trait still has a source
method? If so, should it be implemented (specialized) only when there is a unique source? For example, if there are many possible sources, should source
return any (the first one) of the possible sources?std::error::Report
, its current design works with a chain of errors. With the new Error
trait, this Report
may lose its functionality since it may not know what context it should request. A "generic formattable report" that works with any context may not be possible. Will the guideline be that each (binary) crate codes its own Report
type, and that std::error::Report
is used only if one expects a chain (and not a tree) of errors?
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.