6
6
# Summary
7
7
8
8
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.
14
18
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.
17
20
18
21
# Motivation
19
22
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.
23
28
24
29
For example, the rustdoc for [ ` std::io::ErrorKind ` ] shows:
25
30
@@ -48,7 +53,8 @@ pub enum ErrorKind {
48
53
```
49
54
50
55
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:
52
58
53
59
``` rust
54
60
use std :: io :: ErrorKind :: * ;
@@ -86,9 +92,10 @@ match error_kind {
86
92
}
87
93
```
88
94
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.
90
97
91
- ## How we do this today
98
+ ### How we do this today
92
99
93
100
We force users add this arm for [ ` std::io::ErrorKind ` ] by adding a hidden
94
101
variant:
@@ -123,9 +130,9 @@ pub enum Error {
123
130
}
124
131
```
125
132
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 :
129
136
130
137
``` rust
131
138
use diesel :: Error :: * ;
@@ -139,8 +146,8 @@ match error {
139
146
}
140
147
```
141
148
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
144
151
library remarks that this is a hack. Recall the hidden variant for
145
152
[ ` std::io::ErrorKind ` ] :
146
153
@@ -158,7 +165,7 @@ Additionally, while plenty of crates could benefit from the idea of
158
165
non-exhaustiveness, plenty don't because this isn't documented in the Rust book,
159
166
and only documented elsewhere as a hack until a better solution is proposed.
160
167
161
- ## Opportunity for optimisation
168
+ ### Opportunity for optimisation
162
169
163
170
Currently, the ` #[doc(hidden)] ` hack leads to a few missed opportunities
164
171
for optimisation. For example, take this enum:
@@ -205,10 +212,13 @@ Although these options will unlikely matter in this example because
205
212
error-handling code (hopefully) shouldn't run very often, it could matter for
206
213
other use cases.
207
214
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.
209
220
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:
212
222
213
223
``` rust
214
224
pub struct Config {
@@ -259,11 +269,17 @@ But this makes it more difficult for the crate itself to construct `Config`,
259
269
because you have to add a ` non_exhaustive: () ` field every time you make a new
260
270
value.
261
271
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
+
262
278
# Detailed design
263
279
264
280
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 .
267
283
268
284
## Enums
269
285
@@ -311,15 +327,23 @@ match error {
311
327
And it should * not* be marked as dead code, even if the compiler does mark it as
312
328
dead and remove it.
313
329
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
+
314
336
## Structs
315
337
316
338
Like with enums, the attribute is essentially ignored in the crate that defines
317
339
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.
321
345
322
- For example, using our ` Config ` again:
346
+ Using our ` Config ` again:
323
347
324
348
``` rust
325
349
#[non_exhaustive]
@@ -352,23 +376,62 @@ Users can still match on `Config`s non-exhaustively, as usual:
352
376
let & Config { window_width , window_height , .. } = config ;
353
377
```
354
378
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:
357
389
358
390
``` rust
359
- let val = Config {
360
- window_width : 640 ,
361
- window_height : 480 ,
362
- .. Default :: default ()
363
- };
391
+ pub Config (pub u16 , pub u16 , ());
364
392
```
365
393
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:
368
396
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
+ ```
372
435
373
436
## Changes to rustdoc
374
437
@@ -400,7 +463,8 @@ time.
400
463
Additionally, non-exhaustive structs should be documented in an early chapter on
401
464
structs. Public fields should be preferred over getter/setter methods in Rust,
402
465
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.
404
468
405
469
# Drawbacks
406
470
@@ -413,16 +477,18 @@ breaking change.
413
477
* Provide a dedicated syntax instead of an attribute. This would likely be done
414
478
by adding a ` ... ` variant or field, as proposed by the original
415
479
[ 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 .
418
482
* Document the ` #[doc(hidden)] ` hack and make it more well-known.
419
483
420
484
# Unresolved questions
421
485
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.
426
492
427
493
## Extending to traits
428
494
0 commit comments