-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Error type redesign #1131
Comments
Have you considered using Anyway, maybe @brson himself could say if |
Can you declare the |
A backtrace is neat (but expensive?). I'm not sure I'd want to expose any It's worth thinking about the things some would do with an
Anything else? I'm thinking of having methods on the type to allow inspecting the type of error it was, since it's always possible to add more methods. Like, |
@seanmonstar: I do not think it is a problem if programmers do exhaustive matching on the I use Update: Update: |
@sanmai-NL That's interesting, are you able to share a bit about what you do with specific |
No sorry, I do nothing with |
Maybe translate error messages into another language. |
Another general comment: in error handling the viewpoint of regular people needs to be taken into account. I have found this requires tight developer control over error reporting. Backtraces etc. are not very user friendly (may even instill fear), and leak information about the source code, which from a business perspective is intellectual property. For |
@dtolnay Oh that's interesting... I've never thought of translating errors before. I guess you could do that on an arbitrary text stream though, rather than individual error variants?
@sanmai-NL I have the opposite opinion on this with respect to libraries like For application code though I agree having an individual error type per operation is nicer to manage.
I think that's a good point. Returning backtraces in responses isn't very good. But having backtraces in logging is very good, and makes issues much easier to diagnose. This is especially true in Rust where you could be handling an error downstream from where it was produced, so the backtrace you get out-of-the-box might be really unhelpful. Maybe for As far as mapping a |
It is a bit of a problem. With exhaustive matching, that means that adding a new error case is a breaking change. For instance, if at some point it was desirable to separate the
A possibility is
This definitely true if someone were to print the a
That's actually part of the point of this issue. There are 2 rather distinct cases of failure in when using a |
Relevant to the design of type, from this comment:
In terms of internal variants, things I see that would be useful to group are:
|
Wouldn't this be a breaking change regardless of whether hyper's error type is an enum or not? If it was a struct that could be inspected somehow, it would still potentially break peoples code, just at runtime rather than at compile time, no? |
@nicoburns not necessarily. If it were a struct you could inspect, that'd probably be some method, like The current enum design makes it impossible to add any detail to a variant, since that requires changing the types of values that can matched publicly, so this extra detail could never be provided without a breaking change. |
+1 for the code based solution - then there can be a static lookup mechanism (array) to go from code to message. And that would be compatible. |
Something that I've wanted that is easily done in class-based languages is to be able to split up an error variant into sub-variants in the future, and not breaking existing code. With Classes (not Rust)For example, say there was a try:
print(client.get("https://hyper.rs"))
except error.Connect:
# maybe retry Splitting the errors: class Resolve(error.Connect):
pass
class Handshake(error.Connect):
pass And the original exception code will continue to work, and anyone upgrading can update to handle the two cases individually, or continue to just treat them all as In RustIn Rust, we don't have classes that can extend like this, so we must use a different strategy to try to achieve this forward compatibility. With MethodsIf all this information is in private fields, and a user can only ask questions with methods, it is possible. if err.is_connect() {
// retry?
} Adding The downside here is that it doesn't work very nicely with pattern matching. With EnumsWe could do this and support pattern matching, but it could get verbose. With a if let Err(err) = await client.get(url) {
match err.kind() {
ErrorKind::Connect(_) => {
// retry?
},
_ => {},
}
} If we split the #[non_exhaustive]
enum ErrorKind {
Connect(Connect),
}
#[non_exhaustive]
enum Connect {
Resolve(Resolve),
Handshake(Handshake),
}
struct Resolve(());
struct Handshake(()); Then, the original code still works, because the top type is still if let Err(err) = await client.get(url) {
match err.kind() {
ErrorKind::Connect(Connect::Resolve(_)) => {
// don't retry?
},
ErrorKind::Connect(Connect::Handshake(_)) => {
// retry?
},
_ => {},
}
} |
Regarding just a naming aspect in this design. Chrono has a similar use of the same names for both a variant and nested sub-type. There at least, it can be challenging to get the enum Connect {
OnResolve(Resolve),
OnHandshake(Handshake),
} |
@dekellum You mean if trying to import the variants as well? |
yes, to make it more compact.
|
I hope we all avoid glob imports, but every importable item should have a unique name irrespective of that. |
Has the (unstable) #[non_exhaustive] attribute been considered to address the problem of breaking changes? IMO this is a preferable solution, since as a user I would like to be confident that I'm handling every possible error type. This attribute allows for a lint which would let you know when a new error type has been added, and should be handled, without causing a compiler error. It doesn't address the issue of users creating their own errors, but that seems like a lesser concern to me. |
Unless something's changed since the RFC, |
I was referring to the possibility of a Clippy lint, or compiler warning in future, which would appear after a minor update in which new variants had been added (see the "Unresolved questions" section). |
That would definitely make the feature more pragmatic, less dogmatic, and better for users. |
I've started doing this e.g. a public, documented, unused |
I came here via #1652. I think that methods
If the number of internal errors is large and there is a concern for frequent breaking changes, I am greatly in favour of a two-stage classification. Use one enum for the general error category, and other enums for more granular, specific errors. At the very least the general category should be made public so an API consumer can match on it. |
I've recently been having a hard time trying to find a way to get to the actual cause of an error that Hyper gives me. For example, when I receive an error that looks like this:
I would like to be able to, somehow, extract the |
Is it still the case that users of this crate should not create |
It would be nice if instead of the current |
The
Error
type in hyper is currently anenum
, wrapping up the various cases of things that could go wrong when parsing incoming HTTP data. Some of the problems with the current design:enum
means that adding variants is a breaking change. There is a__Nonexhaustive
variant to try to warn that exhaustive matches can cause breaking changes, but that it's better when internals are hidden.hyper::Error
s, even though they really shouldn't. It is meant only for saying "this thing went wrong while hyper was parsing HTTP", and nothing else. Part of this can be fixed also by changing the actual error types used, such as in outgoingStream
s.Instead, I'd like to propose making the
Error
an opaquestruct
, with some methods to inspect the type and details of an error, somewhat along the lines ofstd::io::Error
.(Still thinking through the specific code, will add a design section later).
The text was updated successfully, but these errors were encountered: