Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document how non_exhaustive interacts with tuple and unit-like structs. #1491

Merged
merged 2 commits into from
Apr 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 47 additions & 13 deletions src/attributes/type_system.md
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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);
Expand All @@ -64,30 +72,47 @@ 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 visibility of the same-named constant of a [unit-like struct][struct]
is lowered to `min($vis, pub(crate))`.
- The visibility of the same-named constructor function of a [tuple struct][struct]
is lowered to `min($vis, pub(crate))`.
kpreid marked this conversation as resolved.
Show resolved Hide resolved
- [`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;
```
Expand All @@ -100,11 +125,13 @@ There are limitations when matching on non-exhaustive types outside of the defin
- 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 {
Expand All @@ -118,6 +145,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 } => { },
Expand Down