---
title: "Design meeting 2024-07-10: Discriminant syntax (RFC 3607)"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2024-07-10
discussion: https://rust-lang.zulipchat.com/#narrow/stream/410673-t-lang.2Fmeetings/topic/Design.20meeting.202024-07-10
url: https://hackmd.io/LqSf7RnsR0CX4xSNE_GP_g
---
https://github.com/rust-lang/rfcs/pull/3607
- Feature Name: `direct_enum_discriminant`
- Start Date: 2024-03-16
- RFC PR: [rust-lang/rfcs#3607](https://github.com/rust-lang/rfcs/pull/3607)
- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000)
# Summary
[summary]: #summary
Enable using **`.enum#discriminant`** on values of enum type from safe code in the same module
to get the numeric value of the variant's discriminant in the numeric type of its `repr`.
# Motivation
[motivation]: #motivation
Today in Rust you can use `as` casts on *field-less* `enum`s to get their discriminants,
but as soon as any variant has fields, that's no longer available.
Rust 1.66 stabilized custom discriminants on variants with fields, but as
[the release post][rust 1.66 blog] said,
> Rust provides no language-level way to access the raw discriminant of an enum with fields.
> Instead, currently unsafe code must be used to inspect the discriminant of an enum with fields.
[rust 1.66 blog]: https://blog.rust-lang.org/2022/12/15/Rust-1.66.0.html#explicit-discriminants-on-enums-with-fields
As a result, the [documentation for `mem::Discriminant`][discriminant docs] has a section
about how to write that `unsafe` code, and a bunch of warnings about the different
*incorrect* ways that must not be used.
[discriminant docs]: https://doc.rust-lang.org/std/mem/fn.discriminant.html#accessing-the-numeric-value-of-the-discriminant
It's technically [possible](https://github.com/rust-lang/rust/pull/106418#issuecomment-1700399884)
to write a clever enough safe `match` that compiles down to a no-op in order to get at the discriminant,
but doing so is annoying and fragile.
And accessing the discriminant is quite useful in various places, so it'd be nice for it to be easy.
For example, `#[derive(PartialOrd)]` on an `enum` today uses internal compiler magic to look at discriminants.
It would be nice for other derives in the ecosystem -- there's a whole bunch of things on `enum`s --
to be able to look at the discriminants directly too.
With this RFC, the built-in derives and third-party derives can both use the same stable feature
to implement `PartialOrd::parial_cmp` for the cases where the arguments have different discriminants.
# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation
[Rust 1.66][rust 1.66 blog] stabilized custom discriminants on enum variants,
but didn't give a nice way to actually read them.
In this release, you can use **`.enum#discriminant`** to read them.
For example, if you have the following enum,
```rust
#[repr(u8)]
enum Enum {
Unit = 7,
Tuple(bool) = 13,
Struct { a: i8 } = 42,
}
```
Then the following examples pass:
```rust
let a = Enum::Unit;
assert_eq!(a.enum#discriminant, 7);
let b = Enum::Tuple(true);
assert_eq!(b.enum#discriminant, 13);
let c = Enum::Struct { a: 1 };
assert_eq!(c.enum#discriminant, 42);
```
That's entirely safe code, and the value comes out as the type from the `repr`,
avoiding the change to accidentally use a mismatched type.
To avoid making implicit semver promises, this is only available for `enum`s
that are defined in the current module. If you want to expose it to others,
feel free to define a method like
```rust
impl Enum {
pub fn discriminant(&self) -> u8 {
self.enum#discriminant
}
}
```
for others to use, or use one of the many derive macros on crates.io
to expose it through a trait implementation.
# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation
## Lexing
In edition 2021 and later, `enum#discrimant` becomes a legal token,
using part of the syntax space previously reserved
in [RFC#3101](https://rust-lang.github.io/rfcs/3101-reserved_prefixes.html).
This means that
```rust
macro_rules! single_tt {
($x:tt) => {}
}
single_tt!(enum#discrimant);
```
now matches, instead of being a lexical error.
In editions 2015 and 2018, this feature is not available.
## Parsing
A new form of expression is added,
> *DiscriminantExpression* :
> > *Expression* `.` `enum#discriminant`
Like `.await`, this is *not* a place expression, and as such is invalid on the
left-hand side of an assignment, giving an error like the following:
```text
error[E0070]: invalid left-hand side of assignment
--> src/lib.rs:5:29
|
5 | x.enum#discriminant = 4;
| ------------------- ^
| |
| cannot assign to this expression
```
## Visibility
This acts as though it were a `pub(in self)` field on a type.
As such, it's an error to use `.enum#discriminant` on types from sub-modules or other crates.
```rust
mod inner {
pub enum Foo { Bar }
}
inner::Foo::Bar.enum#discriminant // ERROR: enum discriminant is private
```
## Type
The LHS is auto-deref'd until it finds something known to be an `enum`.
*Note: this is different from `mem::discriminant`. For example,*
```rust
#![allow(enum_intrinsics_non_enums)]
enum MyEnum { A, B }
let a = Box::new(MyEnum::A);
let b = Box::new(MyEnum::B);
assert_eq!(std::mem::discriminant(&a), std::mem::discriminant(&b));
assert_ne!(a.enum#discriminant, b.enum#discriminant);
```
<!-- https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=76293c2f83cb7719c22c15bcebeaeb13 -->
For this, a generic parameter is never considered to be an `enum`,
although a generic enum where some of the generic parameters to the
enum constructor are not yet known is fine.
It's an error if, despite deref'ing, the LHS is still not an `enum`.
If the enum has `repr(uN)` or `repr(iM)`, the `.enum#discriminant` expression
returns a value of type `uN` or `iM` respectively.
If the enum does not specify an integer `repr`, then it returns `isize`.
*Note: `isize` is rarely the desired type for discriminants, and indeed custom
discriminants on types with fields are disallowed without explicit `repr` types.
Returning `isize` is fine here, though, thanks to privacy because the code
inside the module can be updated should it change to specify a specific type.*
## Semantics
When the LHS of a discriminant expression is a *place*, that place is read but not consumed.
*Note: this can be thought of as if it read a field of `Copy` type from the LHS.*
This lowers to [`Rvalue::Discriminant`][MIR discr] in MIR.
[MIR discr]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.Rvalue.html#variant.Discriminant
As this expression is an *r-value*, not a *place*, `&foo.enum#discriminant` returns
a reference to a temporary, aka is the same as `&{foo.enum#discriminant}`.
It does *not* return a reference to the memory in which the discriminant is
stored -- not even for types that do store the discriminant directly.
This expression is allowed in `const` contexts, but is not promotable.
*Note: the behaviour of this expression is independent of whether the type gets
layout-optimized. For example, the following holds even if `x` is `2_i8` in memory.*
```rust
enum MyOption<T> { MyNone, MySome(T) }
let x = MyOption::<std::cmp::Ordering>::MyNone;
assert_eq!(x.enum#discriminant, 0_isize);
```
# Drawbacks
[drawbacks]: #drawbacks
This isn't strictly necessary, we could continue to get along just fine without it.
- For the FFI cases the layout guarantees mean it's already possible to write a
sound and reliable function that reads the discriminant.
- For cases without `repr(int)`, custom discriminants aren't even allowed,
so those discriminants much not be all that important.
- It's always possible to write a `match` in safe code that optimizes away
and produces exactly the same thing that this new expression would.
- A pseudo-field with `#` in the name looks kinda weird.
- There might be a nicer way to do this in the future.
# Rationale and alternatives
[rationale-and-alternatives]: #rationale-and-alternatives
## Why have a `#` in the name?
By not being an identifier, `.enum#discriminant` can't conflict with anything.
While today there are no fields directly accessible from values of enum type,
there are lots of plausible-enough proposals that would allow some.
For example, *enum variant types* have come up repeatedly, which would represent a single
variant and thus would allow accessing the fields on that type, but plausibly would
still offer access to the discriminant. Similarly, a *pattern type* that restricts
the enum to a single variant would plausibly allow access to its fields. And one
of those fields might be named `discriminant`.
Other requests have come in too, like allowing field access if every variant has
a field with the same name & type or allowing field access if there's only a
single inhabited variant.
By being clearly different it means it can't conflict with any field or method.
That also helps resolve any concerns about it *looking* like field access -- as
existed for `.await` -- since it's visibly lexically different.
And the lexical space is already reserved,
## Why have `enum` in the name?
Well, it seemed short and evocative enough to be fine.
Doing something like `e#` isn't shorter enough to matter, and
I'd rather save very-short prefixes for higher-prevalence things.
And since it's a pre-existing keyword, it means that
```rust
let d = foo().bar.enum#discriminant;
```
already gets highlighting on the `enum` in my editor without needing any updates.
## Isn't this kinda long?
Not really, compared to the existing possibilities.
For example, in a macro expansion even the internal magic today ends up being
```rust
let __self_tag = ::core::intrinsics::discriminant_value(self);
let __arg1_tag = ::core::intrinsics::discriminant_value(other);
::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag)
```
to avoid any accidental shadowing.
In comparison,
```rust
let __self_tag = self.enum#discriminant;
let __arg1_tag = other.enum#discriminant;
::core::cmp::PartialOrd::partial_cmp(&__self_tag, &__arg1_tag)
```
is much easier.
Outside of macros, something like
```rust
discriminant(&foo)
```
(which requires a `use std::mem::discriminant;`)
isn't that different from
```rust
foo.enum#discriminant
```
And of course you can always make a function to give it a shorter name -- or write
a proc macro to generate that function -- if you so wish.
## Why just `pub(in self)`?
The primary use case that led to this RFC is using it in `derive` macros, where
`pub(in self)` is entirely sufficient.
And by being only private, it avoids forcing any semver promises on library authors.
Today, as a library author, you can reorder the variants in an enum should you so wish,
or in a `#[non_exhausive]` enum add new ones in the middle. There's no way for
the users of your library to care about the order in which you defined the variants
(unless you make other documented promises) -- especially if you never `derive(PartialOrd)`.
Any library author who wishes to provide discriminant stability can always write
a function to expose those discriminants, trivially implemented using this feature.
## Why expose it via `.`?
I like it behaving kinda like a field. For example, having auto-deref like a field
means you don't need to worry about whether you actually have a `&&Enum` in a `filter`
or you actually have a `Box<Enum>` or whatever.
Of course, if the `enum` is `repr(C)`, then the discriminant [is a field][RFC2195]
in the guaranteed FFI layout, so thinking of it kinda like a field isn't too weird.
There has also been talk of *compressed* or *move-only* fields where getting the
address is disallowed so that Rust can run arbitrary logic whenever they're accessed
and thus have the freedom to do more layout optimizations than are otherwise possible.
Should we have something like that, then it's again not unreasonable to think of it
as a field that sometimes has particularly fancy layout optimization.
[RFC2195]: https://rust-lang.github.io/rfcs/2195-really-tagged-unions.html
## What about if it was a magic method instead?
It could be. But it would still need to be something that doesn't cause name
resolution failures for other methods that people might already have written.
So I don't think that the extra `()` on it would really improve things.
## Why not allow writing to the discriminant?
The semantics for that get really complicated, especially for `enum`s in `repr(Rust)`
that don't have a guaranteed layout, and even more so those that get layout-optimized.
Maybe one day it could be allowed, but for now this RFC sticks only things that
can be allowed in safe code without worries.
## Couldn't this be a magic macro?
Sure, it could, like `offset_of!`.
I don't think `enum_discriminant!(foo)` is really better than `foo.enum#discriminant`, though.
It doesn't deal in tokens, and there's no special logic to apply to the scope in which
the argument is computed.
It works on a value or place, not on anything dealing tokens, nor does it affect a scope.
## Why not do *\<more complex feature\>*?
Privacy is the problem.
If we wanted to just expose everything's discriminant to everyone, it'd be easy
to have a trait in core that's auto-implemented for every `enum`.
But to do things in a way that doesn't add a new category of major breaking change,
that gets harder.
It'd be great if we had scoped trait impls, for example, so we could do that
in a way where it's up to the trait author how visible things get. But that's
a *massive* feature, so it would be nice not to block on it.
Or libs-api could create a new trait and a new `derive` that's implemented using
the same magic that today's `derive(PartialOrd)` uses. But that's another big
bikeshed, and doesn't even work very well for the "I'm writing my own customized
derive" cases that just want to use the discriminant internally.
The goal here is to do something easy using syntactic space that's not particularly
valuable anyway -- if people end up almost never using this directly because there's
a popular community `derive`, that's great.
## What about `as`?
While `as` *works* on field-less enums, it's not that great there either.
It has the fundamental problem that you have to write out the target type that you want,
and the wrong one will silently truncate. This hits the same general "`as` is error-prone"
theme that is pushing people away from using `as` to using more-specific things
instead that are either lossless or clearer, to help avoid mistakes.
If this exists, I wouldn't be surprised to see people using `foo.enum#discriminant`
even in places where `foo as u8` works and is shorter since you don't have to think
"what was the `repr` of this, again?" and you just get the right thing.
Should the enum's declared `repr` not be the type you actually want, you can always
use `.enum#discriminant` and *then* `as` cast it -- or hopefully `.into()` or
something else with clearer intent -- into the type you need.
# Prior art
[prior-art]: #prior-art
C++'s `std::variant` has an [`index`](https://en.cppreference.com/w/cpp/utility/variant/index)
method, which always returns `std::size_t` since there's no custom discriminants.
(It's more like what rustc calls a *variant index* internally.)
# Unresolved questions
[unresolved-questions]: #unresolved-questions
- Is auto-deref worth it? I would propose leaving it in the RFC for merging,
as wanting to use this on `&Enum` will be common, but if in the course of
implementing it's particularly annoying then stabilizing without it would
be tolerable, since error messages could suggest the correct thing.
# Future possibilities
[future-possibilities]: #future-possibilities
If this turns out to work well, there's a variety of related properties of things
which could be added in ways similar to this.
For example, you could imagine `MyEnum::enum#VARIANT_COUNT` saying how many variants
are declared, `MyEnum::enum#ReprType` to get the type of the discriminant, or
`my_enum.enum#variant_index` to get the declaration-order index of the variant
(as opposed to its *discriminant* value).
Those are *much* easier to generate with a proc macro, however, so are not included
in this RFC. They would need separate motivation from what's done here.
---
# Discussion
## Attendance
- People: TC, scottmcm, tmandry, Josh, nikomatsakis, Xiang, Waffle, thomas, Dow Street, Mara
## Meeting roles
- Minutes, driver: TC
## High level
scottmcm: Big questions for this meeting:
- Is doing something field-like good here, or should it look more like an operator? (In MIR it's an rvalue on a place, not a projection, though there's also a special `SetDiscriminant`.) Would we ever want to allow writing a discriminant (`unsafe`ly).
- Do we consider this a plumbing feature, in which case something "ugly" is ok because it keeps it from taking valuable syntax space, or do we want it to be a porcelain feature that people use directly? (The RFC treats it as the former, to try to be minimal as the goal was to unblock some proc macro uses, where the syntax doesn't really need to be nice.)
## Explanation as a field makes this much clearer
Josh: I previously found the proposed syntax confusing, because I parsed it as a two-level scoping: `.enum` followed by `#discriminant`. This was the origin of my suggestion to write things like `::discriminant` rather than `#discriminant`, because I was parsing this as a `discriminant` pseudo-field inside an `enum` pseudo-field. The syntax makes much more sense now that I'm seeing it as a field named `enum#discriminant`. I think it'd be helpful to describe/document it that way, as a field named `enum#discriminant`.
I no longer think `::discriminant` is a good idea for the value. (It might make sense to have a pseudo-*associated-type* for the discriminant that uses `::enum#DISCRIMINANT`, but that's not this proposal.)
One other syntax that *might* make sense would be `.enum.discriminant`, which "stands out" a little less, which may be a feature or a bug. In any case, `.enum#discriminant` seems fine, and using this same namespace for other purposes also seems fine.
scottmcm: Correct, the goal is that you think of it like `foo.r#crate`, just a field.
scottmcm: That associated type is called `::enum#ReprType` in the future work, so I agree it's a potentially-interesting future extension, but as you say, not this RFC.
## MIR
nikomatsakis: I *believe* MIR has a way of talking about the discriminant already... have to double check this.
scottmcm: yes, [`Rvalue::Discriminant`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.Rvalue.html#variant.Discriminant) and [`StatementKind::SetDiscriminant`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/mir/enum.StatementKind.html#variant.SetDiscriminant) (those are linked to the rustc docs).
nikomatsakis: Yeah, ok, that's not what we really want. We'd want it to be part of the definition of a place. I remember considering that at some point -- maybe in the borrow checker. Regardless I think this model of "discriminant as (real-only) field" aligns well-ish there. Well, ok, maybe what I said is not true, just because it looks like a field doesn't make it a place (and I guess it wouldn't be, right?).
## This is a value expression, not a place expression, right?
nikomatsakis: In particular you cannot do e.g. `&foo.enum#discriminant`...? Or maybe we want to allow that if the repr permits it, that could be...super useful for unsafe code. Future extension perhaps. :)
scottmcm: Ralf has a comment about this: <https://github.com/rust-lang/rfcs/pull/3607#discussion_r1554847086>
> Instead of making this an rvalue, I think we should make it a "read-only move-only place" -- a place you can read from but not write to or create a pointer to. That would be more forward-compatible with actually making this a move-only place once these exist. The difference is that with a move-only place I would think `&foo.enum#discriminant` is an error rather than creating a temporary.
>
> `let m = &mut foo.enum#discriminant; *m = 5;` would be accepted under your proposal, but probably not behave the way the user expects. If they _want_ the copy they can always write `let m = &mut {foo.enum#discriminant};` (and the error message can indicate that).
>
> There's also subtle differences between place and value expressions wrt whether `let _ = (*ptr).enum#discriminant` performs a load or not.
NM: My guess is that this should be a place expression and that we give errors for future compatibility.
Josh: I'd hope we could just put this in future work.
scottmcm: Ralf's point here is that we have to decide this now.
NM: Ralf's comment is where I landed, more or less, except that Ralf doesn't allow for it being a normal place in case of `repr(C)`, but that's probably a future extension.
Josh: +1 for making it a read-only move-only place, or a read-only place that you can't take the address of.
NM: I don't see an issue. It's not a thing we've had before but it's something we've wanted, e.g., move-only fields for layout optimizations.
Josh: However we handle it, as long as taking the address of it is an error, sounds fine.
Josh: This would require adding braces for cases like `hashtable.get(&{ e.enum#discriminant })`, but we could remove those if we made it into an actual place.
## Scoping
Josh: I agree that we can ship a mechanism for now that assumes `pub(in self)` (a private field). Can we plan ahead for how we *would* handle making this available as a public field in the future For instance, a suggestion:
```rust
#[repr(pub u32)]
enum
```
scottmcm: already started an RFC <https://hackmd.io/88BzSUnqR0agN5ubN8bmsQ?view=#Discriminant-types> for `enum Foo: pub u32` and `enum Bar: pub(in qux) u32` and `enum AsciiChar : u8`.
Josh: Nice, that sounds entirely reasonable. Request: could we split out the `: discriminant_type` and `: vis discriminant_type` parts of that as a separate RFC from the new `fieldless`/`struct_with_union`/etc, so that we can FCP them independently?
scottmcm: Notably, both C++ and C# use `: ty` syntax,
```cpp
enum class Handle : std::uint32_t { Invalid = 0 };
```
<https://en.cppreference.com/w/cpp/language/enum>
```cs
enum ErrorCode : ushort
{
None = 0,
Unknown = 1,
ConnectionLost = 100,
OutlierReading = 200
}
```
<https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/enum>
Josh: And this would accept any type, but not any *repr*, so we don't have to reserve syntax space within it for things like `C` that are not types?
scott: Right, it can be the normal type grammar production, leaving `repr` just for layout stuff, rather than things that affect what type discriminant value expressions are.
## RFC 2195?
TC: The document here says:
> To avoid making implicit semver promises, this is only available for `enum`s that are defined in the current module.
Does [RFC 2195](https://github.com/rust-lang/rfcs/pull/2195) come into this at all? That RFC gave defined meaning to:
```rust
#[repr(C, u8)]
pub enum E {
V1 { inner: u8 },
V2(u8),
}
```
That meaning can be relied on rather widely.
scottmcm: This RFC is intentionally super-narrow, so is trying to do the minimum possible that will address the needs of the people who were asking for things like `Discriminant: Ord`.
I'm also writing another RFC ([early partial draft](https://hackmd.io/88BzSUnqR0agN5ubN8bmsQ?view=#Discriminant-types)) to change how we do discriminant types and add a visibility to them, which would be how this could extend to more situations. See also Josh's comment above.
RFC 2195 is one of the reasons I'm writing this RFC. The combination of that RFC and something that we FCPed a few months back gave guaranteed stable FFI meaning to:
```rust
#[repr(Rust, u8)]
pub enum E {
V1 { inner: u8 },
V2(u8),
}
```
So I'll soon propose that the answer for "people should be able to do this" is to change the type definition to:
```rust
#[repr(C, union_of_structs)]
pub enum E: pub u8 {
V1 { inner: u8 },
V2(u8),
}
```
My mental model here is that this feature, in this RFC, is mostly for use in proc macros, so the narrowest visibility is OK here. We may want to expand this to other things in the future, and we can do that in a follow-up RFC.
## Where do we rustdoc this?
Josh: Can we make sure that this shows up in rustdoc searches both under the term `enum` and the term `discriminant`, as well as in the existing documentation for the `enum` keyword https://doc.rust-lang.org/std/keyword.enum.html ? You should also get an indication (similar to trait impls) on the documentation page for any specific enum showing that `enum#discriminant` exists.
## Future possibility: associated discriminant type
EDIT: This is already covered by future work, as `::enum#ReprType`.
Josh: An item for the Future Work section: an associated type `::enum#DISCRIMINANT` for the type. This would make it easy to write (or generate), for instance:
```rust
impl From<EnumType> for EnumType::enum#DISCRIMINANT {
fn from(e: EnumType) {
e.enum#discriminant
}
}
```
## Require explicit discriminant type?
TC: The document says:
> *Note: `isize` is rarely the desired type for discriminants, and indeed custom discriminants on types with fields are disallowed without explicit `repr` types. Returning `isize` is fine here, though, thanks to privacy because the code inside the module can be updated should it change to specify a specific type.*
Maybe it would be better to only allow this when a specific type is set explicitly, e.g. with `#[repr(u8)]`, so that if we were to later relax the visibility requirements, that we would not run into the mentioned issue?
Josh: Given Scott's proposal for how to handle visibility in the future, it sounds like you won't be *able* to specify a non-private visibility for the discriminant without also supplying an explicit discriminant type. e.g. `enum E: pub u32`. Given that, I don't think it'd be a problem to allow the implicit discriminant type for the private case.
TC: Makes sense. Probably what I'm thinking here is that this RFC is trying to be minimal, and the analysis just seems a bit easier if this were restricted further in this way. Will study further.
scottmcm: One of the key bits here is that people want this for proc macros that can apply to all enums, even those without specified `#[repr(..)]` types.
TC: +1; that is an important bit to note.
---
Josh: That said: are enums actually guaranteed to use `isize` here, when they don't have a type specified? Do we not use "smallest type that'll fit"? Should we return the type we actually used, rather than `isize`?
scottmcm: Yes, it's actually `isize`, because that's what `Discriminant` in MIR returns, and the type of the discriminant value in the type definition. I wish we could `repr(Rust, u8)` on `Option` to stop getting `isize` for it, but we can't because that makes it not layout optimize :(.
Josh: Is there any way we can add a mechanism to automatically determine the repr type without disabling layout optimization? For instance, `enum E: pub move u8`, once we have move-only types; that would mean the compiler doesn't have to make the discriminant a place in memory so the compiler can layout-optimize it with niches and similar.
scottmcm: The `isize` is already observable:
```rust
enum Foo {
A = u16::MAX, // ERROR: type mismatch, needs to be `isize` already.
B = isize::MAX, // Success, because these are `isize` when there's no repr.
}
```
Josh: It's surprising that's `isize` and not `usize`. Maybe that's something we want to consider for a future edition.
## Relation to `mem::discriminant`
tmandry: I'm trying to work out how we explain the different ways of interacting with discriminants to users. With this proposal there would be at least three:
* `mem::discriminant` for equality comparisons; can be done with any enum.
* `as` casts for `repr(int)` without fields; can be done with any enum.
* `.enum#discriminant` for enums in the same module; always becomes an integer.
tmandry: It feels a little uneven.
scottmcm: There's a couple of possible directions for this. If we do something like the `: pub u32` thing I mentioned, I'd suggest that we discourage `as` casts generally. One nice thing about `.enum#discriminant` is that you get the correct type.
scottmcm: Also, I'm not sure that anyone actually likes `mem::discriminant`. People actually want the number, but `mem::discriminant` is the minimum that we can do without imposing weird requirements on other types.
scottmcm: This also hits the porcelain question.
## "A generic parameter is never considered to be an `enum`"
Josh: For the autoderef behavior, do we want to plan ahead for the possibility of a `trait enum { type enum#ReprType; }`, so that people *can* write functions that are generic but know they have an enum? That may be something we could specify as part of the RFC defining `ReprType`.
```rust
fn double_tag<E, R>(e: E) -> E::enum#ReprType
where
E: enum,
E::enum#ReprType: Mul<Output: E::enum#ReprType>,
{
e.enum#discriminant * 2
}
```
tmandry: One limitation would be that we can't have private traits.
Waffle: Note that we can't do inherent associated types; we don't have them right now, and there are problems with adding them on the implementation side.
scottmcm: The private traits is why I didn't propose something like that, yes. A standard library derive for something would be potentially interesting, or...
## Bikeshed: Drop the `enum#`
tmandry: `.discriminant` doesn't conflict with anything today. If it does conflict with a future feature we can add a way of disambiguating then, which could be `.r#discriminant` for a field named `discriminant`, as we've done for `.await`.
tmandry: This gets to the question of whether this is "plumbing". My take is that people will want to write this by hand; not everyone wants to add a proc macro dependency to write it for them, and this proposal makes it easy enough to write yourself.
Josh: I think people are *absolutely* going to want to use this in many places, as a substitute for using `as`.
nikomatsakis: A proposal...
```rust
enum Foo { Bar, Baz }
let f: Foo;
assert_eq!(Foo::#len, 2);
assert_eq!(foo.#len, 2); // where I want to get "variant count", essentially
assert_eq!(Foo::Bar.#discriminant, 0);
```
Josh: nikomatsakis, are you suggesting that we have the same mechanism and just change `enum#` to `#` as the generic prefix for *all* built-in reserved names?
nikomatsakis: Basically yes, just saying I would like it to not be a "keyword" but `enum#` is maybe more namespacing than is required. Plus `len` would be useful :).
scottmcm: Spitballing: `.k#tag` but we never actually reserve it as a "real" keyword? (Not sure whether I actually like that more or less than just `.#tag`, though).
nikomatsakis: Not opposed, one nice thing is it gives us the option to drop `k#` in the future more "gracefully".
tmandry: If we had variant types but a static way of getting discriminant values for variants, we could avoid a conflict...
```rust!
fn foo(thing: &Foo::Bar) {
// If I need discriminant of `thing` here, I can write...
let discr = Foo::Bar::DISCRIMINANT;
}
```
tmandry: Common fields would be nice, it occurs to me that move-only fields is kind of what we need...
nikomatsakis: ...but you want more than just copy types.
## Bikeshed: Should we use a shorter name than "discriminant"?
Josh: We've historically referred to this as an enum's "discriminant", but that's been in documentation and conversation and similar. (And `mem::discriminant`, due to its type, has been an obscure and not commonly used feature.) Now that we're making this something new and very visible and very convenient, should we consider using a shorter name, like `.enum#tag`? (Or, per niko's above suggestion, just `.#tag`.)
TC: If this is something we want to do, we might want to decide this for:
https://github.com/rust-lang/rfcs/pull/3659
("`repr(discriminant = ...)` for type aliases")
...as well.
Josh: I think that RFC may be entirely superseded by scottmcm's proposal for `enum E: pub Type`.
scottmcm: I have two days to open it 😬.
Josh: I filed a concern on that RFC.
TC: +1 for using shorter names when possible.
scottmcm: I like "tag". That's the name that RFC 2195 used; some of the examples called it "tag".
NM: +1 on "tag".
TC: With a shorter name like `tag`, the proposal above for `.k#tag` seems more appealing to me than it would otherwise.
Josh: I'd propose that we only use `k#` when we're planning to make it an actual keyword, so I'd be mildly opposed to using that here. I do like the `.#tag` version.
tmandry: Generally, I'm in favor of `tag` over `discriminant`.
tmandry: If we can't reason our way out of the idea that this wouldn't be ambiguous, then maybe the `.#tag` makes sense.
scottmcm: Actually, as it turns out, we don't have raw `#tag` reserved as a token (see below).
(TC: But, as we discussed later, this turns out to not matter, as we can just have the compiler handle this sequence of tokens after lexing.)
## Can we have this for variant names and not just full values?
Josh: Given `enum E { V1(u8), V2(u8) }`, could we have `E::V1.#tag` and not just `E::V1(0).#tag`? Rationale:
```rust
match e.#tag {
E::V1.#tag => ...,
E::V2.#tag => ...,
}
```
Waffle: That would be difficult to handle.
tmandry: How about `E::V1::#tag`?
Josh: +1.
```rust
match e.#tag {
E::V1::#tag => ...,
E::V2::#tag => ...,
}
```
## What's reserved?
```rust
macro_rules! foo {
($x:tt) => {}
}
fn main() {
foo!(#foo);
}
```
...gives:
```text
error: no rules expected the token `foo`
--> src/main.rs:6:11
|
1 | macro_rules! foo {
| ---------------- when calling this macro
...
6 | foo!(#foo);
| ^^^ no rules expected this token in macro call
|
note: while trying to match meta-variable `$x:tt`
--> src/main.rs:2:6
|
2 | ($x:tt) => {}
| ^^^^^
```
<https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=aa217d0e259b2e850e7a19c9a5745310>