Merge pull request #1491 from kpreid/neunit

Document how `non_exhaustive` interacts with tuple and unit-like structs.
This commit is contained in:
Eric Huss 2024-04-15 19:59:53 +00:00 committed by GitHub
commit 585b9bcb72
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 52 additions and 15 deletions

View File

@ -20,6 +20,12 @@ pub struct Config {
pub window_height: u16,
}
#[non_exhaustive]
pub struct Token;
#[non_exhaustive]
pub struct Id(pub u64);
#[non_exhaustive]
pub enum Error {
Message(String),
@ -34,11 +40,13 @@ pub enum Message {
// Non-exhaustive structs can be constructed as normal within the defining crate.
let config = Config { window_width: 640, window_height: 480 };
let token = Token;
let id = Id(4);
// Non-exhaustive structs can be matched on exhaustively within the defining crate.
if let Config { window_width, window_height } = config {
// ...
}
let Config { window_width, window_height } = config;
let Token = token;
let Id(id_number) = id;
let error = Error::Other;
let message = Message::Reaction(3);
@ -64,30 +72,49 @@ Non-exhaustive types cannot be constructed outside of the defining crate:
- Non-exhaustive variants ([`struct`][struct] or [`enum` variant][enum]) cannot be constructed
with a [_StructExpression_] \(including with [functional update syntax]).
- The implicitly defined same-named constant of a [unit-like struct][struct],
or the same-named constructor function of a [tuple struct][struct],
has a [visibility] no greater than `pub(crate)`.
That is, if the structs visibility is `pub`, then the constant or constructors visibility
is `pub(crate)`, and otherwise the visibility of the two items is the same
(as is the case without `#[non_exhaustive]`).
- [`enum`][enum] instances can be constructed.
The following examples of construction do not compile when outside the defining crate:
<!-- ignore: requires external crates -->
```rust,ignore
// `Config`, `Error`, and `Message` are types defined in an upstream crate that have been
// annotated as `#[non_exhaustive]`.
use upstream::{Config, Error, Message};
// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};
// Cannot construct an instance of `Config`, if new fields were added in
// Cannot construct an instance of `Config`; if new fields were added in
// a new version of `upstream` then this would fail to compile, so it is
// disallowed.
let config = Config { window_width: 640, window_height: 480 };
// Can construct an instance of `Error`, new variants being introduced would
// Cannot construct an instance of `Token`; if new fields were added, then
// it would not be a unit-like struct any more, so the same-named constant
// created by it being a unit-like struct is not public outside the crate;
// this code fails to compile.
let token = Token;
// Cannot construct an instance of `Id`; if new fields were added, then
// its constructor function signature would change, so its constructor
// function is not public outside the crate; this code fails to compile.
let id = Id(5);
// Can construct an instance of `Error`; new variants being introduced would
// not result in this failing to compile.
let error = Error::Message("foo".to_string());
// Cannot construct an instance of `Message::Send` or `Message::Reaction`,
// Cannot construct an instance of `Message::Send` or `Message::Reaction`;
// if new fields were added in a new version of `upstream` then this would
// fail to compile, so it is disallowed.
let message = Message::Send { from: 0, to: 1, contents: "foo".to_string(), };
let message = Message::Reaction(0);
// Cannot construct an instance of `Message::Quit`, if this were converted to
// Cannot construct an instance of `Message::Quit`; if this were converted to
// a tuple-variant `upstream` then this would fail to compile.
let message = Message::Quit;
```
@ -95,16 +122,18 @@ let message = Message::Quit;
There are limitations when matching on non-exhaustive types outside of the defining crate:
- When pattern matching on a non-exhaustive variant ([`struct`][struct] or [`enum` variant][enum]),
a [_StructPattern_] must be used which must include a `..`. Tuple variant constructor visibility
is lowered to `min($vis, pub(crate))`.
a [_StructPattern_] must be used which must include a `..`. A tuple variant's constructor's
[visibility] is reduced to be no greater than `pub(crate)`.
- When pattern matching on a non-exhaustive [`enum`][enum], matching on a variant does not
contribute towards the exhaustiveness of the arms.
The following examples of matching do not compile when outside the defining crate:
<!-- ignore: requires external crates -->
```rust, ignore
// `Config`, `Error`, and `Message` are types defined in an upstream crate that have been
// annotated as `#[non_exhaustive]`.
use upstream::{Config, Error, Message};
// These are types defined in an upstream crate that have been annotated as
// `#[non_exhaustive]`.
use upstream::{Config, Token, Id, Error, Message};
// Cannot match on a non-exhaustive enum without including a wildcard arm.
match error {
@ -118,6 +147,13 @@ if let Ok(Config { window_width, window_height }) = config {
// would compile with: `..`
}
// Cannot match a non-exhaustive unit-like or tuple struct except by using
// braced struct syntax with a wildcard.
// This would compile as `let Token { .. } = token;`
let Token = token;
// This would compile as `let Id { 0: id_number, .. } = id;`
let Id(id_number) = id;
match message {
// Cannot match on a non-exhaustive struct enum variant without including a wildcard.
Message::Send { from, to, contents } => { },
@ -147,3 +183,4 @@ Non-exhaustive types are always considered inhabited in downstream crates.
[enum]: ../items/enumerations.md
[functional update syntax]: ../expressions/struct-expr.md#functional-update-syntax
[struct]: ../items/structs.md
[visibility]: ../visibility-and-privacy.md