std::io::ErrorKind
has a variant __Nonexhaustive
.
I don't know what is the problem if this variant doesn't exist.
What's purpose of this variant?
The purpose of this hidden variant is to prevent you from writing something like this (which doesn't compile precisely because of the presence of __Nonexhaustive
):
use std::io::ErrorKind;
fn main() {
let error_kind: ErrorKind = unimplemented!();
match error_kind {
ErrorKind::NotFound => unimplemented!(),
ErrorKind::PermissionDenied => unimplemented!(),
ErrorKind::ConnectionRefused => unimplemented!(),
ErrorKind::ConnectionReset => unimplemented!(),
ErrorKind::ConnectionAborted => unimplemented!(),
ErrorKind::NotConnected => unimplemented!(),
ErrorKind::AddrInUse => unimplemented!(),
ErrorKind::AddrNotAvailable => unimplemented!(),
ErrorKind::BrokenPipe => unimplemented!(),
ErrorKind::AlreadyExists => unimplemented!(),
ErrorKind::WouldBlock => unimplemented!(),
ErrorKind::InvalidInput => unimplemented!(),
ErrorKind::InvalidData => unimplemented!(),
ErrorKind::TimedOut => unimplemented!(),
ErrorKind::WriteZero => unimplemented!(),
ErrorKind::Interrupted => unimplemented!(),
ErrorKind::Other => unimplemented!(),
ErrorKind::UnexpectedEOF => unimplemented!(),
ErrorKind::UnexpectedEof => unimplemented!(),
// note: no wildcard match arm here
};
}
The reason why the developers of the standard library don't want you to do this is to retain the ability to add variants to ErrorKind
in the future. The __Nonexhaustive
variant prevents you from doing an exhaustive match by simply handling each individual variant; you must have a wildcard arm to have an exhaustive match.
In Rust, the match
expression requires that all possible patterns for the expression being matched on have a corresponding arm, so that the match
expression always has a well-defined, explicit value. A match
that covers all patterns is called an exhaustive match. With enum
s, Rust lets us simply list all variants. For example, with Option
, which only has 2 variants, named None
and Some
, we can write:
fn main() {
let option: Option<()> = unimplemented!();
match option {
None => unimplemented!(),
Some(()) => unimplemented!(),
};
}
This match
compiles fine, because it covers all possible patterns for option
. However, if the Option
type gained another variant, then suddenly your code wouldn't compile anymore, because it would no longer be exhaustive. Naturally, this wouldn't make sense for Option
, so the Option
type doesn't play the "nonexhaustive" game. But if __Nonexhaustive
wasn't there, adding a variant to ErrorKind
would be a breaking change; any code that did an exhaustive match (without wildcards) on an ErrorKind
would suddenly stop compiling. This code might be in a crate that your application depends on, and that breakage could prevent you from upgrading Rust until that crate is fixed!
It is designed to allow the ErrorKind
enum to be expanded in future, by forcing any match
statements in stable code to have a catch-all _
arm.
Specifically, the variant is marked unstable and so cannot be referred to on the stable channel, and so the compiler rejects code like
fn foo(x: Error) {
match x.kind() {
ErrorKind::NotFound => {}
ErrorKind::PermissionDenied => {}
ErrorKind::ConnectionRefused => {}
ErrorKind::ConnectionReset => {}
ErrorKind::ConnectionAborted => {}
ErrorKind::NotConnected => {}
ErrorKind::AddrInUse => {}
ErrorKind::AddrNotAvailable => {}
ErrorKind::BrokenPipe => {}
ErrorKind::AlreadyExists => {}
ErrorKind::WouldBlock => {}
ErrorKind::InvalidInput => {}
ErrorKind::InvalidData => {}
ErrorKind::TimedOut => {}
ErrorKind::WriteZero => {}
ErrorKind::Interrupted => {}
ErrorKind::Other => {}
ErrorKind::UnexpectedEof => {}
ErrorKind::UnexpectedEOF => {}
ErrorKind::__Nonexhaustive => {}
}
}
<anon>:24:9: 24:35 error: use of unstable library feature 'io_error_internals': better expressed through extensible enums that this enum cannot be exhaustively matched against (see issue #0)
<anon>:24 ErrorKind::__Nonexhaustive => {}
^~~~~~~~~~~~~~~~~~~~~~~~~~
If this code compiled successfully on stable Rust, then adding a variant to ErrorKind
in a future version would break any code that had match
s like the above, and breaking stable code is bad. The code breaks because matches in Rust must be exhaustive, that is, they must cover every possibility, in some way, and thus adding a variant would mean that possibility isn't covered.
Instead, programmers must write:
fn foo(x: Error) {
match x.kind() {
ErrorKind::NotFound => {}
ErrorKind::PermissionDenied => {}
ErrorKind::ConnectionRefused => {}
ErrorKind::ConnectionReset => {}
ErrorKind::ConnectionAborted => {}
ErrorKind::NotConnected => {}
ErrorKind::AddrInUse => {}
ErrorKind::AddrNotAvailable => {}
ErrorKind::BrokenPipe => {}
ErrorKind::AlreadyExists => {}
ErrorKind::WouldBlock => {}
ErrorKind::InvalidInput => {}
ErrorKind::InvalidData => {}
ErrorKind::TimedOut => {}
ErrorKind::WriteZero => {}
ErrorKind::Interrupted => {}
ErrorKind::Other => {}
ErrorKind::UnexpectedEof => {}
ErrorKind::UnexpectedEOF => {}
_ => {}
}
}
This means that any variants added to ErrorKind
in future (e.g. new error possibilities for new IO functions) will fall under the _
arm, and thus existing stable code won't break.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With