From 24e3b5e7d3ad433e78f9999b6d8483e5681d0147 Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Mon, 31 Oct 2022 15:20:44 -0600 Subject: [PATCH 1/5] Add exhaustive_output RFC --- text/0000-exhaustive-output.md | 109 +++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 text/0000-exhaustive-output.md diff --git a/text/0000-exhaustive-output.md b/text/0000-exhaustive-output.md new file mode 100644 index 00000000000..c0f6b7119f5 --- /dev/null +++ b/text/0000-exhaustive-output.md @@ -0,0 +1,109 @@ +- Feature Name: exhaustive_match_output +- Start Date: 2022-10-31 +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Add an attribute which is only applicable to match statements called `#[exhaustive_output]` which will verify that +the match statement has an arm for every variant of the enum type which it returns. This attribute will not be +applicable to match statements that don't return an enum type. + +# Motivation +[motivation]: #motivation + +Oftentimes it is necessary to convert unrestricted values into enum variants. Unrestricted values can include integers, strings, UUIDs, even other much larger enums. +When adding a new variant to the output enum variant, it's possible the author forgot to update the match statement which converts from unrestricted values, thus leaving +the new enum variant unusable. This attribute would prevent this scenario, by alerting the author that they didn't update the input match statement. + +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +## exhaustive_output + +The `#[exhaustive_output]` attribute will cause a compile time warning to be emitted if the match statement beneath it doesn't have an arm for all possible output variants. +You might want this when parsing input from integers, strings, or UUIDs and mapping that input to enum variants. + +### Example + +```rust +pub enum Kind { + Cat, + Dog, + Lizard, +} + +impl From for Kind { + fn from(o: u32) -> Kind { + #[exhaustive_output] // <-- Warning emitted, Kind::Lizard is not covered. + match o { + 1 => Kind::Cat, + 2 => Kind::Dog, + _ => panic!("Unknown kind"); + } + } +} +``` + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +A new warning will be added to rustc with diagnostics like + +``` +non-exhaustive output in match statement that requires exhaustive output + +note: enum variant :: is not returned by any arm in this match statement. Please add an arm that returns ::. +``` + +This warning can be changed to deny with a module or crate attribute like +`#![deny(exhaustive_output_missing)]` + +The warning will be triggered when a match statement tagged with the attribute is found and that match statement does not have arms for all possible variants\* of the +enum which it returns. + +\* Please note this is **not** "all possible values", it is "all possible variants". This means that if an enum variant is a tuple or struct variant, it is not necessary +to exhaustively populate the fields of the variant. This lint will only trigger if a variant is missing from the possible outputs of the match statement. + +# Drawbacks +[drawbacks]: #drawbacks + +This is yet another attribute for the language, and it's possible it will be difficult to teach exactly how it works and in what situations you should use it. Maybe this can be fixed by +making the proposed educational material easier to read and understand. + +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +The most clear alternative is "don't add this attribute" so let's consider the options for someone who wants to avoid this mistake without using this feature + +### Use the `strum_macros` crate to automatically generate these types of match statements + +This is actually a pretty good idea, though it's not compatible with some representations of enum values, such as UUIDs and more complex data structures. +Additionally there may be significant differences between how the data is represented in user input, and how you want to represent it in your enum. Finally, +maybe you just don't want to add a dependency for this. + +### Author your own macro which combines "defining the enum" and "pairing the enum with its external id" + +This is much more robust than the aforementioned, though it is a good deal more labor and you might only reach for it if this option occurs to you. + +# Prior art +[prior-art]: #prior-art + +The author is not aware of any prior art on this, but will happily add some to the RFC if that changes. + +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +None at this time. + +# Future possibilities +[future-possibilities]: #future-possibilities + +This attribute as written must be used + +- With a match statement +- That returns an enum type + +It might be possible to expand this to match statements which return trait objects in the future, though the trait would have to be private as you couldn't truly meet +this restriction for a public trait. From 5d0878ca354693532156929e1118430289b7a245 Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Mon, 31 Oct 2022 15:26:08 -0600 Subject: [PATCH 2/5] Update links --- text/{0000-exhaustive-output.md => 3340-exhaustive-output.md} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename text/{0000-exhaustive-output.md => 3340-exhaustive-output.md} (96%) diff --git a/text/0000-exhaustive-output.md b/text/3340-exhaustive-output.md similarity index 96% rename from text/0000-exhaustive-output.md rename to text/3340-exhaustive-output.md index c0f6b7119f5..66221de43a2 100644 --- a/text/0000-exhaustive-output.md +++ b/text/3340-exhaustive-output.md @@ -1,7 +1,7 @@ - Feature Name: exhaustive_match_output - Start Date: 2022-10-31 -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) -- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) +- RFC PR: [rust-lang/rfcs#3340](https://github.com/rust-lang/rfcs/pull/3340) +- Rust Issue: [rust-lang/rust#103818](https://github.com/rust-lang/rust/issues/103818) # Summary [summary]: #summary From 5d287b58a5da04ceb960bcf9c6b96afce262bbea Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Mon, 31 Oct 2022 15:51:19 -0600 Subject: [PATCH 3/5] Grammar fix --- text/3340-exhaustive-output.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3340-exhaustive-output.md b/text/3340-exhaustive-output.md index 66221de43a2..8d96958635a 100644 --- a/text/3340-exhaustive-output.md +++ b/text/3340-exhaustive-output.md @@ -14,7 +14,7 @@ applicable to match statements that don't return an enum type. [motivation]: #motivation Oftentimes it is necessary to convert unrestricted values into enum variants. Unrestricted values can include integers, strings, UUIDs, even other much larger enums. -When adding a new variant to the output enum variant, it's possible the author forgot to update the match statement which converts from unrestricted values, thus leaving +When adding a new variant to the output enum, it's possible the author forgot to update the match statement which converts from unrestricted values, thus leaving the new enum variant unusable. This attribute would prevent this scenario, by alerting the author that they didn't update the input match statement. # Guide-level explanation From bd0b16622d17347a70d49db6e75c0e8ea459d012 Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Mon, 31 Oct 2022 16:54:07 -0600 Subject: [PATCH 4/5] Add clarification about attribute Co-authored-by: Josh Triplett --- text/3340-exhaustive-output.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3340-exhaustive-output.md b/text/3340-exhaustive-output.md index 8d96958635a..74cf0e722b3 100644 --- a/text/3340-exhaustive-output.md +++ b/text/3340-exhaustive-output.md @@ -66,6 +66,8 @@ enum which it returns. \* Please note this is **not** "all possible values", it is "all possible variants". This means that if an enum variant is a tuple or struct variant, it is not necessary to exhaustively populate the fields of the variant. This lint will only trigger if a variant is missing from the possible outputs of the match statement. +Applying `#[exhaustive_output]` to anything other than a `match` statement will produce an error. + # Drawbacks [drawbacks]: #drawbacks From d1aaa90eb0b2a752dc3b07cd3686d52f37bd8a16 Mon Sep 17 00:00:00 2001 From: Jacob Kiesel Date: Sat, 5 Nov 2022 09:32:34 -0600 Subject: [PATCH 5/5] Mention dead code lint in alternatives --- text/3340-exhaustive-output.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/3340-exhaustive-output.md b/text/3340-exhaustive-output.md index 74cf0e722b3..a12cc2430dd 100644 --- a/text/3340-exhaustive-output.md +++ b/text/3340-exhaustive-output.md @@ -77,7 +77,10 @@ making the proposed educational material easier to read and understand. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives -The most clear alternative is "don't add this attribute" so let's consider the options for someone who wants to avoid this mistake without using this feature +One alternative is to rely on the dead code lint, which will inform you when a variant hasn't been constructed. However it will fail to catch this if the +variant is constructed elsewhere, or the enum is public. + +Other alternatives would be to try and implement something like this outside of rustc. Let's consider those options. ### Use the `strum_macros` crate to automatically generate these types of match statements