std::io::ErrorKind
имеет вариант __Nonexhaustive
.
Я не знаю, в чем проблема, если этого варианта не существует.
Какая цель этого варианта?
std::io::ErrorKind
имеет вариант __Nonexhaustive
.
Я не знаю, в чем проблема, если этого варианта не существует.
Какая цель этого варианта?
Он разработан, чтобы позволить расширять перечисление ErrorKind
в будущем, заставляя любые операторы match
в стабильном коде иметь захват _
.
В частности, вариант отмечен неустойчивым, поэтому его нельзя ссылаться на стабильный канал, поэтому компилятор отклоняет код, например
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 => {}
^~~~~~~~~~~~~~~~~~~~~~~~~~
Если этот код успешно скомпилирован на стабильной Rust, то добавление варианта к ErrorKind
в будущей версии приведет к поломке любого кода, который имел match
, как указано выше, а нарушение стабильного кода плохо. Код ломается, потому что совпадения в Rust должны быть исчерпывающими, то есть они должны каким-то образом покрывать каждую возможность, и, таким образом, добавление варианта означает, что возможность не покрывается.
Вместо этого программисты должны написать:
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
):
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
};
}
Причина, по которой разработчики стандартной библиотеки не хотят, чтобы вы это делали, заключается в сохранении возможности добавления вариантов в ErrorKind
в будущем. Вариант __Nonexhaustive
не позволяет вам выполнять исчерпывающее соответствие, просто обрабатывая каждый индивидуальный вариант; вы должны иметь подстановочный знак, чтобы иметь исчерпывающее соответствие.
В Rust выражение match
требует, чтобы все возможные шаблоны для согласованного выражения имели соответствующий рычаг, так что выражение match
всегда имеет четко определенное явное значение. A match
, который охватывает все шаблоны, называется исчерпывающим. С enum
s, Rust позволяет нам просто перечислить все варианты. Например, с Option
, который имеет только 2 варианта, названных None
и Some
, мы можем написать:
fn main() {
let option: Option<()> = unimplemented!();
match option {
None => unimplemented!(),
Some(()) => unimplemented!(),
};
}
Этот match
компилируется отлично, потому что он охватывает все возможные шаблоны для Option
. Однако, если тип Option
получил другой вариант, то вдруг ваш код больше не компилируется, потому что он больше не будет исчерпывающим. Естественно, это не имеет смысла для Option
, поэтому тип Option
не играет в "неисчерпаемую" игру. Но если __Nonexhaustive
не было, добавление варианта к ErrorKind
было бы изменением; любой код, который сделал исчерпывающее соответствие (без подстановочных знаков) на ErrorKind
, внезапно прекратил компиляцию. Этот код может быть в ящике, от которого зависит ваше приложение, и этот полом может помешать вам обновить Rust до тех пор, пока этот ящик не будет исправлен!