Skip to content

Commit 61a4b14

Browse files
author
Clar Charr
committed
Add notes on tuple/unit structs, variants.
1 parent c358154 commit 61a4b14

File tree

1 file changed

+113
-47
lines changed

1 file changed

+113
-47
lines changed

text/0000-non-exhaustive.md

Lines changed: 113 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,25 @@
66
# Summary
77

88
This RFC introduces the `#[non_exhaustive]` attribute for enums and structs,
9-
which indicates that more variants/public fields may be added to an enum in the
10-
future. Adding this hint to enums will force downstream crates to add a wildcard
11-
arm to `match` statements, ensuring that adding new variants is not a breaking
12-
change. Adding this hint to structs will prevent downstream crates from
13-
constructing values, because more fields may be added.
9+
which indicates that more variants/fields may be added to an enum/struct in the
10+
future.
11+
12+
Adding this hint to enums will force downstream crates to add a wildcard arm to
13+
`match` statements, ensuring that adding new variants is not a breaking change.
14+
15+
Adding this hint to structs or enum variants will prevent downstream crates
16+
from constructing or exhaustively matching, to ensure that adding new fields is
17+
not a breaking change.
1418

15-
This is a post-1.0 version of [RFC 757], modified to use an attribute instead of
16-
a custom syntax, and extended to structs.
19+
This is a post-1.0 version of [RFC 757], with some additions.
1720

1821
# Motivation
1922

20-
The most common use of this feature is error types. Because adding features to a
21-
crate may result in different possibilities for errors, it makes sense that more
22-
types of errors will be added in the future.
23+
## Enums
24+
25+
The most common use for non-exhaustive enums is error types. Because adding
26+
features to a crate may result in different possibilities for errors, it makes
27+
sense that more types of errors will be added in the future.
2328

2429
For example, the rustdoc for [`std::io::ErrorKind`] shows:
2530

@@ -48,7 +53,8 @@ pub enum ErrorKind {
4853
```
4954

5055
Because the standard library continues to grow, it makes sense to eventually add
51-
more error types. However, this can be a breaking change if we're not careful:
56+
more error types. However, this can be a breaking change if we're not careful;
57+
let's say that a user does a match statement like this:
5258

5359
```rust
5460
use std::io::ErrorKind::*;
@@ -86,9 +92,10 @@ match error_kind {
8692
}
8793
```
8894

89-
Then we can add as many variants as we want!
95+
Then we can add as many variants as we want without breaking any downstream
96+
matches.
9097

91-
## How we do this today
98+
### How we do this today
9299

93100
We force users add this arm for [`std::io::ErrorKind`] by adding a hidden
94101
variant:
@@ -123,9 +130,9 @@ pub enum Error {
123130
}
124131
```
125132

126-
Even though the variant is hidden in the rustdoc, there's nothing actually stopping a
127-
user from using the `__Nonexhaustive` variant. This code works totally fine on
128-
stable rust:
133+
Even though the variant is hidden in the rustdoc, there's nothing actually
134+
stopping a user from using the `__Nonexhaustive` variant. This code works
135+
totally fine, for example:
129136

130137
```rust
131138
use diesel::Error::*;
@@ -139,8 +146,8 @@ match error {
139146
}
140147
```
141148

142-
This is obviously unintended, and this is currently the best way to make
143-
non-exhaustive enums outside the standard library. Plus, even the standard
149+
This seems unintended, even tohugh this is currently the best way to make
150+
non-exhaustive enums outside the standard library. In fact, even the standard
144151
library remarks that this is a hack. Recall the hidden variant for
145152
[`std::io::ErrorKind`]:
146153

@@ -158,7 +165,7 @@ Additionally, while plenty of crates could benefit from the idea of
158165
non-exhaustiveness, plenty don't because this isn't documented in the Rust book,
159166
and only documented elsewhere as a hack until a better solution is proposed.
160167

161-
## Opportunity for optimisation
168+
### Opportunity for optimisation
162169

163170
Currently, the `#[doc(hidden)]` hack leads to a few missed opportunities
164171
for optimisation. For example, take this enum:
@@ -205,10 +212,13 @@ Although these options will unlikely matter in this example because
205212
error-handling code (hopefully) shouldn't run very often, it could matter for
206213
other use cases.
207214

208-
## A case for structs
215+
## Structs
216+
217+
The most common use for non-exhaustive structs is config types. It often makes
218+
sense to make fields public for ease-of-use, although this can ultimately lead
219+
to breaking changes if we're not careful.
209220

210-
In addition to enums, it makes sense to extend this feature to structs as well.
211-
The most common use case for this would be config structs, like the one below:
221+
For example, take this config struct:
212222

213223
```rust
214224
pub struct Config {
@@ -259,11 +269,17 @@ But this makes it more difficult for the crate itself to construct `Config`,
259269
because you have to add a `non_exhaustive: ()` field every time you make a new
260270
value.
261271

272+
### Other kinds of structs
273+
274+
Because enum variants are *kind* of like a struct, any change we make to structs
275+
should apply to them too. Additionally, any change should apply to tuple structs
276+
as well.
277+
262278
# Detailed design
263279

264280
An attribute `#[non_exhaustive]` is added to the language, which will (for now)
265-
fail to compile if it's used on anything other than an enum or struct
266-
definition.
281+
fail to compile if it's used on anything other than an enum, struct definition,
282+
or enum variant.
267283

268284
## Enums
269285

@@ -311,15 +327,23 @@ match error {
311327
And it should *not* be marked as dead code, even if the compiler does mark it as
312328
dead and remove it.
313329

330+
Note that this can *potentially* cause breaking changes if a user adds
331+
`#[deny(dead_code)]` to a match statement *and* the upstream crate removes the
332+
`#[non_exhaustive]` lint. That said, modifying warn-only lints is generally
333+
assumed to not be a breaking change, even though users can make it a breaking
334+
change by manually denying lints.
335+
314336
## Structs
315337

316338
Like with enums, the attribute is essentially ignored in the crate that defines
317339
the struct, so that users can continue to construct values for the struct.
318-
However, this will prevent downstream users from constructing values or
319-
exhaustively matching values, because fields may be added to the struct in the
320-
future.
340+
However, this will prevent downstream users from constructing or exhaustively
341+
matching the struct, because fields may be added to the struct in the future.
342+
343+
Additionally, adding `#[non_exhaustive]` to an enum variant will operate exactly
344+
the same as if the variant were a struct.
321345

322-
For example, using our `Config` again:
346+
Using our `Config` again:
323347

324348
```rust
325349
#[non_exhaustive]
@@ -352,23 +376,62 @@ Users can still match on `Config`s non-exhaustively, as usual:
352376
let &Config { window_width, window_height, .. } = config;
353377
```
354378

355-
But without the `..`, this code will fail to compile. Additionally, the user
356-
won't be able to perform any functional-record-updates like the below:
379+
But without the `..`, this code will fail to compile.
380+
381+
Although it should not be explicitly forbidden by the language to mark a struct
382+
with some private fields as non-exhaustive, it should emit a warning to tell the
383+
user that the attribute has no effect.
384+
385+
## Tuple structs
386+
387+
Non-exhaustive tuple structs will operate similarly to structs, however, will
388+
disallow matching directly. For example, take this example on stable today:
357389

358390
```rust
359-
let val = Config {
360-
window_width: 640,
361-
window_height: 480,
362-
..Default::default()
363-
};
391+
pub Config(pub u16, pub u16, ());
364392
```
365393

366-
Because there's no guarantee that the remaining fields will satisfy the
367-
requirements (in this case, `Default`).
394+
The below code does not work, because you can't match tuple structs with private
395+
fields:
368396

369-
Although it should not be explicitly forbidden by the language to mark a struct
370-
with some private fields as non-exhaustive, it should emit a warning to tell the
371-
user that the attribute has no effect.
397+
```rust
398+
let Config(width, height, ..) = config;
399+
```
400+
401+
However, this code *does* work:
402+
403+
```rust
404+
let Config { 0: width, 1: height, .. } = config;
405+
```
406+
407+
So, if we label a struct non-exhaustive:
408+
409+
```
410+
#[non_exhaustive]
411+
pub Config(pub u16, pub u16)
412+
```
413+
414+
Then we the only valid way of matching will be:
415+
416+
```rust
417+
let Config { 0: width, 1: height, .. } = config;
418+
```
419+
420+
## Unit structs
421+
422+
Unit structs will work very similarly to tuple structs. Consider this struct:
423+
424+
```rust
425+
#[non_exhaustive]
426+
pub struct Unit;
427+
```
428+
429+
We won't be able to construct any values of this struct, but we will be able to
430+
match it like:
431+
432+
```rust
433+
let Unit { .. } = unit;
434+
```
372435

373436
## Changes to rustdoc
374437

@@ -400,7 +463,8 @@ time.
400463
Additionally, non-exhaustive structs should be documented in an early chapter on
401464
structs. Public fields should be preferred over getter/setter methods in Rust,
402465
although users should be aware that adding extra fields is a potentially
403-
breaking change.
466+
breaking change. In this chapter, users should be taught about non-exhaustive
467+
enum variants as well.
404468

405469
# Drawbacks
406470

@@ -413,16 +477,18 @@ breaking change.
413477
* Provide a dedicated syntax instead of an attribute. This would likely be done
414478
by adding a `...` variant or field, as proposed by the original
415479
[extensible enums RFC][RFC 757].
416-
* Allow creating private enum variants, giving a less-hacky way to create a
417-
hidden variant.
480+
* Allow creating private enum variants and/or private fields for enum variants,
481+
giving a less-hacky way to create a hidden variant/field.
418482
* Document the `#[doc(hidden)]` hack and make it more well-known.
419483

420484
# Unresolved questions
421485

422-
Should there be a way to warn downstream crate users when their match is
423-
non-exuhaustive? What if a user wants to add a warning to their matches using
424-
non-exhaustive enums, to indicate that more code should be added to handle a new
425-
variant?
486+
It may make sense to have a "not exhaustive enough" lint to non-exhaustive
487+
enums or structs, so that users can be warned if they are missing fields or
488+
variants despite having a wildcard arm to warn on them.
489+
490+
Although this is beyond the scope of this particular RFC, it may be good as a
491+
clippy lint in the future.
426492

427493
## Extending to traits
428494

0 commit comments

Comments
 (0)