Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's purpose of ErrorKind::__Nonexhaustive?

Tags:

rust

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?

like image 390
tanagumo Avatar asked Apr 06 '16 01:04

tanagumo


2 Answers

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 enums, 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!

like image 50
Francis Gagné Avatar answered Nov 03 '22 17:11

Francis Gagné


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 matchs 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.

like image 27
huon Avatar answered Nov 03 '22 17:11

huon