Rust RFC 2008で規定されている#[non_exhaustive]
属性について簡単に解説します
- RFC 2008: https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md
- Rust Issue: https://github.com/rust-lang/rust/issues/44109
- Unstable book: https://doc.rust-lang.org/unstable-book/language-features/non-exhaustive.html
この内容は安定化されておらずnightlyでしか使えません 1.40 (2019/12/20) で安定化されています
Motivation
ライブラリを設計するとき、実装が進むにつれて新たにエラーを定義する必要が出てきます。Rustではエラーを扱うのに主にenumを使いますが、この要素は将来増える可能性があります。例えばstd::io::ErrorKind
を見てみましょう
pub enum ErrorKind {
NotFound,
PermissionDenied,
ConnectionRefused,
ConnectionReset,
ConnectionAborted,
NotConnected,
AddrInUse,
AddrNotAvailable,
BrokenPipe,
AlreadyExists,
WouldBlock,
InvalidInput,
InvalidData,
TimedOut,
WriteZero,
Interrupted,
Other,
UnexpectedEof,
// some variants omitted
}
このエラーをハンドリングするために次のようなmatch
文を書いたとします
use std::io::ErrorKind::*;
match error_kind {
NotFound => ...,
PermissionDenied => ...,
ConnectionRefused => ...,
ConnectionReset => ...,
ConnectionAborted => ...,
NotConnected => ...,
AddrInUse => ...,
AddrNotAvailable => ...,
BrokenPipe => ...,
AlreadyExists => ...,
WouldBlock => ...,
InvalidInput => ...,
InvalidData => ...,
TimedOut => ...,
WriteZero => ...,
Interrupted => ...,
Other => ...,
UnexpectedEof => ...,
}
これは実装した段階では動きますが、将来ErrorKind
に新たなエラーが追加されたときに正しくハンドリングできなくなります。しかし
match error_kind {
// ...
_ => ...,
}
のように_
ブランチが用意されていれば将来にわたって正しく動作することが期待できます。
non_exhaustive
attribute
現在ではこの問題に対処するために、例えばdiesel::error::Error
はライブラリレベルで次のような方法をとっています:
pub enum Error {
InvalidCString(NulError),
DatabaseError(String),
NotFound,
QueryBuilderError(Box<StdError+Send+Sync>),
DeserializationError(Box<StdError+Send+Sync>),
#[doc(hidden)]
__Nonexhaustive,
}
このように隠された要素を追加することによって、__Nonexhaustive
が見える範囲では網羅的なマッチが可能で、それより外では網羅的なマッチを禁止しています。
これを簡単に実現するのがnon_exhaustive
属性です
#[non_exhaustive]
pub enum Error {
Message(String),
Other,
}
のように定義することで、このenumが定義されたcrate内では網羅的なマッチが可能となり、外からは
use mycrate::Error;
match error {
Message(ref s) => ...,
Other => ...,
_ => ...,
}
のようにアクセスする必要があります。
特に述べませんが、structのマッチにおいても同様の問題が発生するため、non_exhaustive
属性はstructに対しても適用できます。