hyper: Error type redesign
The Error
type in hyper is currently an enum
, wrapping up the various cases of things that could go wrong when parsing incoming HTTP data. Some of the problems with the current design:
- Being an
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. - Users sometimes try to create
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 opaque struct
, with some methods to inspect the type and details of an error, somewhat along the lines of std::io::Error
.
(Still thinking through the specific code, will add a design section later).
About this issue
- Original URL
- State: open
- Created 7 years ago
- Comments: 29 (14 by maintainers)
I came here via #1652. I think that methods
is_*
on anError
struct are very unergonomic and a mistake in general.if/else if
chain.is_*
methods when the internal enum is extended, leading to holes in the API.is_*
methods convey no information about which classes of errors there are (also see previous point). Theis_*
methods might be implemented for a small subset, they might overlap, there is no way to know! Contrast this with enums where you know everything is mutually exclusive, and you know (at least for the current version in use) that the set is exhaustive.is_*
methods might be too granular or not granular enough for their use cases.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
io::Error
to match againstio::ErrorKind::ConnectionReset
, since that’s a specific case I’d like to deal with. I don’t think this is possible with the currentError
type, or at least, I haven’t been able to figure out how to do it.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).
Unless something’s changed since the RFC,
#[non_exhaustive]
generates a hard error, not a warning if you try to exhaustively match against of an enum outside of the crate it’s defined in.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
use
imports right without naming ambiguity. If there are natural names that avoid same-names, that can be friendlier, e.g:A backtrace is neat (but expensive?). I’m not sure I’d want to expose any
enum
at all with regards to theError
orErrorKind
, because of the inability to grow it. I’d reconsider when and if Rust ever grows some form of private variants (or however is used to prevent exhaustive matches).It’s worth thinking about the things some would do with an
Error
from hyper.fmt::Debug
andfmt::Display
.Response
depending on the error that occurred? Like, return431
when there was aError::TooManyHeaders
, perhaps a414
if there wereError::TooLarge
, and a400
with other parse errors.io::Error
s, to allow an application to adapt, such as if the error involves too many file descriptors open, or something.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,
error.is_uri_too_long()
or whatever.