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
Prev Previous commit
Next Next commit
Update priv-extend.md
Flesh out the discussion
  • Loading branch information
rcoh committed Jan 6, 2021
commit 9363e0020396f61cb0b6415aa5ec14923a139e4a
35 changes: 29 additions & 6 deletions idioms/priv-extend.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
# `#[non_exhaustive]` for extensibility
# `#[non_exhaustive]` & private fields for extensibility
simonsan marked this conversation as resolved.
Show resolved Hide resolved

## Description

Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants to allow evolution without breaking backwards compatibility.
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:
- Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants. 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).
- For `struct`s only, you may add a private field to struct
simonsan marked this conversation as resolved.
Show resolved Hide resolved

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).
**Warning**

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.

`#[non_exhaustive]` forces clients to handle the "Something else" case; there is rarely a sensible action to take in this scenario. This leads to awkward code and code paths that only executed in extremely rare circumstances.

## Example

Expand Down Expand Up @@ -43,10 +49,27 @@ fn main(s: a::S) {
}
```

simonsan marked this conversation as resolved.
Show resolved Hide resolved
`#[non_exhaustive]` only works across crate boundaries. Within a crate, the private field method may be used:

For `struct`s, an alternative approach exists: By adding a private field to a struct, the struct cannot be instantiated or matched against.

```rust
pub struct S {
pub a: i32,
// Because `b` is private, you cannot match on `S` without using `..` and `S` cannot be directly instantiated
b: ()
rcoh 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 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.
On `struct`s `#[non_exhaustive]` allows adding additional fields in a backwards 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]` when applied to `enum`s forces clients to handle a wildcard variant.

simonsan marked this conversation as resolved.
Show resolved Hide resolved
Finally, #[non_exhaustive] can be applied to enum variants. A `#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` struct.

`#[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.
### Disadvantages
`#[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.

`#[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.
When `#[non_exhaustive]` is applied to `enum`s, it forces clients to handle a wildcard variant. If there is no sensible action to take in this case, this may lead to brittle code. If a client decides to `panic!()` in this scenario, it may have been better to expose this error at compile time.