From a9f0bc8503e04fbe37cc280dd029d7eb93442400 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Sat, 2 Jan 2021 17:04:51 -0500 Subject: [PATCH 01/20] Updated extensibility to discuss non_exhaustive A first cut at discussing how `#[non_exhaustive]` should be used --- idioms/priv-extend.md | 37 +++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index b8d05ace..898fd0e0 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -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 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). ## Example ```rust,ignore 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"); + } } - ``` ## 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. From 14356c2590634722ca60d5cf1e24179670ed11a0 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Sun, 3 Jan 2021 13:18:12 -0500 Subject: [PATCH 02/20] Update idioms/priv-extend.md Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- idioms/priv-extend.md | 1 + 1 file changed, 1 insertion(+) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 898fd0e0..b977a19c 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -6,6 +6,7 @@ Use `#[non_exhaustive]` on `struct`, `enum` and `enum` variant definitions to en 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). + ## Example ```rust,ignore From 4849271a6f0734a3c0ff88e74649b98c8c22d7a8 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Sun, 3 Jan 2021 13:19:33 -0500 Subject: [PATCH 03/20] Update priv-extend.md Updated to match the Rust Doc --- idioms/priv-extend.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index b977a19c..8234a2e3 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -2,8 +2,7 @@ ## Description -Use `#[non_exhaustive]` on `struct`, `enum` and `enum` variant definitions to ensure that a struct is extensible without breaking -stability guarantees. +Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants to allow evolution without breaking backwards compatibility. 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). From 9363e0020396f61cb0b6415aa5ec14923a139e4a Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Tue, 5 Jan 2021 23:32:05 -0500 Subject: [PATCH 04/20] Update priv-extend.md Flesh out the discussion --- idioms/priv-extend.md | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 8234a2e3..02dea05e 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -1,10 +1,16 @@ -# `#[non_exhaustive]` for extensibility +# `#[non_exhaustive]` & private fields for extensibility ## 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 -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 @@ -43,10 +49,27 @@ fn main(s: a::S) { } ``` +`#[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: () +} +``` + ## 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. + +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. From 028350eeefb6ca8418584e3dd4d9b7a2c04c2356 Mon Sep 17 00:00:00 2001 From: Russell Cohen Date: Mon, 18 Jan 2021 23:20:46 -0500 Subject: [PATCH 05/20] Update idioms/priv-extend.md Co-authored-by: Ivan Tham --- idioms/priv-extend.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 02dea05e..df0811d1 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -55,9 +55,9 @@ For `struct`s, an alternative approach exists: By adding a private field to a st ```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: () + pub a: i32, + // Because `b` is private, you cannot match on `S` without using `..` and `S` cannot be directly instantiated + b: () } ``` From 1c7643544e537f3a87b32773e634c2147bcb3617 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 29 Mar 2021 20:26:52 +0200 Subject: [PATCH 06/20] Fixes from review --- idioms/priv-extend.md | 58 ++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index df0811d1..b15e5109 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -2,15 +2,26 @@ ## Description -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). +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 -**Warning** +## 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. +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. +`#[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 @@ -33,43 +44,60 @@ mod a { // Main function in another crate fn main(s: a::S) { - // Because S is `#[non_exhaustive]`, it cannot be named here and we must use `..` - // in the pattern. + // 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 + + // The wildcard match is required because more variants may be + // added in the future _ => println1("it's a new variant"); } } ``` -`#[non_exhaustive]` only works across crate boundaries. Within a crate, the private field method may be used: +`#[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. +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 + // Because `b` is private, you cannot match on `S` without using `..` and `S` + // cannot be directly instantiated b: () } ``` ## Discussion -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. +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. -Finally, #[non_exhaustive] can be applied to enum variants. A `#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` struct. +Finally, #[non_exhaustive] can be applied to enum variants. 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. -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. +`#[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. From c79e976f44676ac2b47f24dc0b7586fe39dd7770 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Mon, 29 Mar 2021 23:56:26 +0200 Subject: [PATCH 07/20] Update idioms/priv-extend.md Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- idioms/priv-extend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index b15e5109..73694604 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -21,7 +21,7 @@ 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. +code paths that are only executed in extremely rare circumstances. ## Example From a68505863f2b6fbe807412b9d28be319efd81920 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 1 Apr 2021 14:34:04 +0200 Subject: [PATCH 08/20] Update idioms/priv-extend.md --- idioms/priv-extend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 73694604..c9a15e86 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -25,7 +25,7 @@ code paths that are only executed in extremely rare circumstances. ## Example -```rust,ignore +```rust mod a { // Public struct. #[non_exhaustive] From 0a453fe46a148c33a448577e1eac51ed31f7a6ee Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 1 Apr 2021 14:34:54 +0200 Subject: [PATCH 09/20] Update idioms/priv-extend.md --- idioms/priv-extend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index c9a15e86..efc350ea 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -64,7 +64,7 @@ fn main(s: a::S) { ``` `#[non_exhaustive]` only works across crate boundaries. Within a crate, the -private field method may be used: +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. From fd8b8739376dbc4b239b328f2c799a2d24e6d2e6 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 1 Apr 2021 14:35:36 +0200 Subject: [PATCH 10/20] Update idioms/priv-extend.md Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- idioms/priv-extend.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index efc350ea..029070c1 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -86,8 +86,6 @@ even if all the fields are public. This may be helpful, but it's worth consideri 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. - Finally, #[non_exhaustive] can be applied to enum variants. A `#[non_exhaustive]` variant behaves in the same way as a `#[non_exhaustive]` struct. From 8cb448a9c3e161d3d372c7e75678034eda8c6f6c Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Thu, 1 Apr 2021 14:36:57 +0200 Subject: [PATCH 11/20] Update idioms/priv-extend.md Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- idioms/priv-extend.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 029070c1..fe61366c 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -63,6 +63,8 @@ fn main(s: a::S) { } ``` +## Alternative approach for structs + `#[non_exhaustive]` only works across crate boundaries. Within a crate, the private field method may be used. From d0c4f083f0eb7f9423f780852c2d2de42d9e8bc5 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 31 Aug 2021 12:52:57 +0200 Subject: [PATCH 12/20] Update priv-extend.md --- idioms/priv-extend.md | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index fe61366c..31939e23 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -63,20 +63,29 @@ fn main(s: a::S) { } ``` -## Alternative approach for structs +## Alternative: `Private fields` for structs `#[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. +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. + +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. ```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: () + // cannot be directly instantiated or matched against + _b: () } ``` @@ -101,3 +110,7 @@ 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. + +## See also + +- [RFC introducing #[non_exhaustive] attribute for enums and structs](https://github.com/rust-lang/rfcs/blob/master/text/2008-non-exhaustive.md) From 4dfff1640e5657b5a7c24e0784178f9a3955a964 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 31 Aug 2021 12:55:35 +0200 Subject: [PATCH 13/20] Update priv-extend.md --- idioms/priv-extend.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 31939e23..2f86a6b7 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -10,7 +10,8 @@ compatibility. Rust offers two solutions: 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 +- You may add a private field to a struct to prevent it from being directly +instantiated or matched against ## Warning From 1d3beb66021651e3347fb4a5b08e7478c7a419f3 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 31 Aug 2021 12:57:49 +0200 Subject: [PATCH 14/20] Update priv-extend.md --- idioms/priv-extend.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 471133e6..a1ce0a41 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -51,15 +51,15 @@ fn main(s: a::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"); + 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"); + 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"); + _ => println1("it's a new variant") } } ``` From d66879dc40ff4ecde4212bc4950ee0da2c05eb2d Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:02:08 +0200 Subject: [PATCH 15/20] Update priv-extend.md --- idioms/priv-extend.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index a1ce0a41..2d74e769 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -43,8 +43,7 @@ mod a { } } -// Main function in another crate -fn main(s: a::S) { +fn print_matched_variants(s: a::S) { // Because S is `#[non_exhaustive]`, it cannot be named here and // we must use `..` in the pattern. let a::S { foo: _, ..} = s; @@ -59,7 +58,7 @@ fn main(s: a::S) { // The wildcard match is required because more variants may be // added in the future - _ => println1("it's a new variant") + _ => println("it's a new variant") } } ``` From 21846307ce6fe6ab94f362e6ea57283bb765fe36 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Tue, 31 Aug 2021 13:03:19 +0200 Subject: [PATCH 16/20] Update priv-extend.md --- idioms/priv-extend.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 2d74e769..fb9e4ea0 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -58,7 +58,7 @@ fn print_matched_variants(s: a::S) { // The wildcard match is required because more variants may be // added in the future - _ => println("it's a new variant") + _ => println!("it's a new variant") } } ``` From 657e55c6176a41b3444899c19581a64b637c5613 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 1 Sep 2021 02:03:46 +0200 Subject: [PATCH 17/20] Apply suggestions from code review Co-authored-by: Marco Ieni <11428655+MarcoIeni@users.noreply.github.com> --- idioms/priv-extend.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index fb9e4ea0..a160b2e1 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -1,26 +1,27 @@ -# `#[non_exhaustive]` & private fields for extensibility +# `#[non_exhaustive]` and private fields for extensibility ## Description 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: +compatibility. +Rust offers two solutions to this problem: - 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 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). - You may add a private field to a struct to prevent it from being directly instantiated or matched against ## Warning -Use this deliberately and with caution: Incrementing the major version when adding +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 +In fact, `#[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 are only executed in extremely rare circumstances. @@ -91,7 +92,7 @@ pub struct S { ## Discussion -On `struct`s `#[non_exhaustive]` allows adding additional fields in a backwards +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 From 86cb855e93b030a56d23a3e5852325b3a6e75f6a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 1 Sep 2021 02:21:45 +0200 Subject: [PATCH 18/20] Update priv-extend.md --- idioms/priv-extend.md | 67 +++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index a160b2e1..d7408a50 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -5,6 +5,7 @@ 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: - Use `#[non_exhaustive]` on `struct`s, `enum`s, and `enum` variants. @@ -12,18 +13,7 @@ Rust offers two solutions to this problem: see [the docs](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute). - You may add a private field to a struct to prevent it from being directly -instantiated or matched against - -## 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. - -In fact, `#[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 are only executed in extremely rare circumstances. + instantiated or matched against (see Alternative) ## Example @@ -66,20 +56,22 @@ fn print_matched_variants(s: a::S) { ## Alternative: `Private fields` for structs -`#[non_exhaustive]` only works across crate boundaries. Within a crate, the -private field method may be used. +`#[non_exhaustive]` only works across crate boundaries. +Within a crate, the private field method may be used. 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. +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. 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. +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. ```rust pub struct S { @@ -93,24 +85,37 @@ pub struct S { ## Discussion 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. +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. -Finally, #[non_exhaustive] can be applied to enum variants. 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. ### 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. +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. +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. ## See also From ef87873591f3773d264af6f59756226a9ebbf4e1 Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 1 Sep 2021 02:23:55 +0200 Subject: [PATCH 19/20] Update priv-extend.md --- idioms/priv-extend.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index d7408a50..4b02e13e 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -9,8 +9,8 @@ compatibility. Rust offers two solutions to this problem: - 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). + 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). - You may add a private field to a struct to prevent it from being directly instantiated or matched against (see Alternative) @@ -114,8 +114,8 @@ 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. +In fact, `#[non_exhaustive]` forces clients to handle the "Something else" +case; there is rarely a sensible action to take in this scenario. ## See also From 77770c1d409c0369d7d503e4ea3213b5d29b715a Mon Sep 17 00:00:00 2001 From: simonsan <14062932+simonsan@users.noreply.github.com> Date: Wed, 1 Sep 2021 02:25:22 +0200 Subject: [PATCH 20/20] Update priv-extend.md --- idioms/priv-extend.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/idioms/priv-extend.md b/idioms/priv-extend.md index 4b02e13e..2a14fdb4 100644 --- a/idioms/priv-extend.md +++ b/idioms/priv-extend.md @@ -114,8 +114,8 @@ 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. +In fact, `#[non_exhaustive]` forces clients to handle the "Something else" case; +there is rarely a sensible action to take in this scenario. ## See also