2021-09-01 00:28:30 +00:00
|
|
|
# `#[non_exhaustive]` and private fields for extensibility
|
2015-12-08 21:59:30 +00:00
|
|
|
|
|
|
|
## Description
|
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
A small set of scenarios exist where a library author may want to add public
|
|
|
|
fields to a public struct or new variants to an enum without breaking backwards
|
|
|
|
compatibility.
|
|
|
|
|
|
|
|
Rust offers two solutions to this problem:
|
|
|
|
|
2023-08-18 18:08:03 +00:00
|
|
|
- Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants. For
|
|
|
|
extensive documentation on all the places where `#[non_exhaustive]` can be
|
|
|
|
used, see
|
|
|
|
[the docs](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute).
|
2021-09-01 00:28:30 +00:00
|
|
|
|
|
|
|
- You may add a private field to a struct to prevent it from being directly
|
|
|
|
instantiated or matched against (see Alternative)
|
2015-12-08 21:59:30 +00:00
|
|
|
|
|
|
|
## Example
|
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
```rust
|
2015-12-08 21:59:30 +00:00
|
|
|
mod a {
|
|
|
|
// Public struct.
|
2021-09-01 00:28:30 +00:00
|
|
|
#[non_exhaustive]
|
2015-12-08 21:59:30 +00:00
|
|
|
pub struct S {
|
|
|
|
pub foo: i32,
|
2021-09-01 00:28:30 +00:00
|
|
|
}
|
2024-03-18 01:34:08 +00:00
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
#[non_exhaustive]
|
|
|
|
pub enum AdmitMoreVariants {
|
|
|
|
VariantA,
|
|
|
|
VariantB,
|
|
|
|
#[non_exhaustive]
|
2024-03-18 01:34:08 +00:00
|
|
|
VariantC {
|
|
|
|
a: String,
|
|
|
|
},
|
2015-12-08 21:59:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
fn print_matched_variants(s: a::S) {
|
|
|
|
// Because S is `#[non_exhaustive]`, it cannot be named here and
|
|
|
|
// we must use `..` in the pattern.
|
2024-03-18 01:34:08 +00:00
|
|
|
let a::S { foo: _, .. } = s;
|
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
let some_enum = a::AdmitMoreVariants::VariantA;
|
|
|
|
match some_enum {
|
|
|
|
a::AdmitMoreVariants::VariantA => println!("it's an A"),
|
|
|
|
a::AdmitMoreVariants::VariantB => println!("it's a b"),
|
|
|
|
|
|
|
|
// .. required because this variant is non-exhaustive as well
|
|
|
|
a::AdmitMoreVariants::VariantC { a, .. } => println!("it's a c"),
|
2015-12-08 21:59:30 +00:00
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
// The wildcard match is required because more variants may be
|
|
|
|
// added in the future
|
2024-03-18 01:34:08 +00:00
|
|
|
_ => println!("it's a new variant"),
|
2021-09-01 00:28:30 +00:00
|
|
|
}
|
|
|
|
}
|
2015-12-08 21:59:30 +00:00
|
|
|
```
|
|
|
|
|
2021-09-01 00:28:30 +00:00
|
|
|
## Alternative: `Private fields` for structs
|
|
|
|
|
2023-08-18 18:08:03 +00:00
|
|
|
`#[non_exhaustive]` only works across crate boundaries. Within a crate, the
|
|
|
|
private field method may be used.
|
2015-12-08 21:59:30 +00:00
|
|
|
|
2023-08-18 18:08:03 +00:00
|
|
|
Adding a field to a struct is a mostly backwards compatible change. However, if
|
|
|
|
a client uses a pattern to deconstruct a struct instance, they might name all
|
|
|
|
the fields in the struct and adding a new one would break that pattern. The
|
|
|
|
client could name some fields and use `..` in the pattern, in which case adding
|
|
|
|
another field is backwards compatible. Making at least one of the struct's
|
|
|
|
fields private forces clients to use the latter form of patterns, ensuring that
|
|
|
|
the struct is future-proof.
|
2021-01-21 23:00:02 +00:00
|
|
|
|
2023-08-18 18:08:03 +00:00
|
|
|
The downside of this approach is that you might need to add an otherwise
|
|
|
|
unneeded field to the struct. You can use the `()` type so that there is no
|
|
|
|
runtime overhead and prepend `_` to the field name to avoid the unused field
|
|
|
|
warning.
|
2021-09-01 00:28:30 +00:00
|
|
|
|
|
|
|
```rust
|
|
|
|
pub struct S {
|
|
|
|
pub a: i32,
|
|
|
|
// Because `b` is private, you cannot match on `S` without using `..` and `S`
|
|
|
|
// cannot be directly instantiated or matched against
|
2024-03-18 01:34:08 +00:00
|
|
|
_b: (),
|
2021-09-01 00:28:30 +00:00
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
## Discussion
|
|
|
|
|
|
|
|
On `struct`s, `#[non_exhaustive]` allows adding additional fields in a backwards
|
2023-08-18 18:08:03 +00:00
|
|
|
compatible way. It will also prevent clients from using the struct constructor,
|
|
|
|
even if all the fields are public. This may be helpful, but it's worth
|
|
|
|
considering if you *want* an additional field to be found by clients as a
|
|
|
|
compiler error rather than something that may be silently undiscovered.
|
|
|
|
|
|
|
|
`#[non_exhaustive]` can be applied to enum variants as well. A
|
|
|
|
`#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]`
|
|
|
|
struct.
|
|
|
|
|
|
|
|
Use this deliberately and with caution: incrementing the major version when
|
|
|
|
adding fields or variants is often a better option. `#[non_exhaustive]` may be
|
|
|
|
appropriate in scenarios where you're modeling an external resource that may
|
|
|
|
change out-of-sync with your library, but is not a general purpose tool.
|
2021-09-01 00:28:30 +00:00
|
|
|
|
|
|
|
### Disadvantages
|
|
|
|
|
2023-08-18 18:08:03 +00:00
|
|
|
`#[non_exhaustive]` can make your code much less ergonomic to use, especially
|
|
|
|
when forced to handle unknown enum variants. It should only be used when these
|
|
|
|
sorts of evolutions are required **without** incrementing the major version.
|
2021-09-01 00:28:30 +00:00
|
|
|
|
|
|
|
When `#[non_exhaustive]` is applied to `enum`s, it forces clients to handle a
|
2023-08-18 18:08:03 +00:00
|
|
|
wildcard variant. If there is no sensible action to take in this case, this may
|
|
|
|
lead to awkward code and code paths that are only executed in extremely rare
|
|
|
|
circumstances. If a client decides to `panic!()` in this scenario, it may have
|
|
|
|
been better to expose this error at compile time. In fact, `#[non_exhaustive]`
|
|
|
|
forces clients to handle the "Something else" case; there is rarely a sensible
|
|
|
|
action to take in this scenario.
|
2021-09-01 00:28:30 +00:00
|
|
|
|
|
|
|
## See also
|
|
|
|
|
|
|
|
- [RFC introducing #[non_exhaustive] attribute for enums and structs](https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md)
|