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

Updated extensibility to discuss non_exhaustive #135

Merged
merged 26 commits into from
Sep 1, 2021
Merged
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
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
37 changes: 27 additions & 10 deletions idioms/priv-extend.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
# Privacy for extensibility
# `#[non_exhaustive]` for extensibility

## Description

Use a private field to ensure that a struct is extensible without breaking
Use `#[non_exhaustive]` on `struct`, `enum` and `enum` variant definitions to ensure that a struct is extensible without breaking
simonsan marked this conversation as resolved.
Show resolved Hide resolved
stability guarantees.


For extensive documentation on all the places `#[non_exhaustive]` can be used, see [the docs](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute).
rcoh marked this conversation as resolved.
Show resolved Hide resolved
## Example

```rust,ignore
simonsan marked this conversation as resolved.
Show resolved Hide resolved
mod a {
// Public struct.
#[non_exhaustive]
pub struct S {
pub foo: i32,
// Private field.
bar: i32,
}

#[non_exhaustive]
pub enum AdmitMoreVariants {
VariantA,
VariantB,
#[non_exhaustive]
VariantC { a: String }
}
}

// Main function in another crate
fn main(s: a::S) {
// Because S::bar is private, it cannot be named here and we must use `..`
// Because S is `#[non_exhaustive]`, it cannot be named here and we must use `..`
// in the pattern.
let a::S { foo: _, ..} = s;

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");
// The wildcard match is required because more variants may be added in the future
_ => println1("it's a new variant");
}
}

```

simonsan marked this conversation as resolved.
Show resolved Hide resolved
## Discussion

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 of the 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.
Adding a field to a struct is a mostly backwards compatible change. However, if a client uses a pattern to construct or 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 of the fields and use `..` in the pattern, in which case adding another field is backwards compatible. Rust provides `#[non_exhaustive]` to prevent clients from using code in a way that may be backwards incompatible in the future.

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.
`#[non_exhaustive]` can also be applied to enums and their variants. A non-exhaustive enum requires that a wildcard variant must be used during matching. A `#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` struct.

If Rust allowed private variants of enums, we could use the same trick to make adding a variant to an enum backwards compatible. The problem there is exhaustive match expressions. A private variant would force clients to have a `_` wildcard pattern.
`#[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.