---
title: "Design meeting 2026-04-22: Open enums"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2026-04-22
discussion: https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202026-04-22.3A.20Open.20enums/
url: https://hackmd.io/atftFIHaQ5OeUVgCh29UzA
---
> [!NOTE]
> Thanks to kupiakos for the RFC.
We'll be reading today the RFC:
https://github.com/rust-lang/rfcs/pull/3894
This is the document:
https://github.com/rust-lang/rfcs/blob/e1f2b9158b29b2868c65eb18c8356c38b33eb8ae/text/3894-unnamed-variants.md
---
# Discussion
## Attendance
- People: Josh, Tomas, Tyler, Jack, TC, Mark (simulacrum), Waffle, Frank Steffahn, Alyssa Haroldsen
## Meeting roles
- Driver: TC
- Minutes: Tomas
## Vibe checks
### Josh
First: I really appreciate this work, and I'm looking forward to using it. This will make FFI with enums much less painful.
I also really appreciate the level of detail in this RFC, and in particular the meticulous handling of cases. Also details like `derive(Debug)` working.
I appreciate the careful lints proposed here, including details like `non_contiguous_range_endpoints`.
> If it is unit-only, it can then be `as` cast from its explicit underlying integer.
We're trying to move *away* from `as` casts. I understand what this is trying to do, but I hope we can do it in another way that doesn't involve `as`. We've talked about dedicated automatic traits for enums, or about being able to `derive` `From` or `TryFrom` in both directions. (This is not a blocker; as the RFC notes, we're working on such mechanisms for enums in general.)
I would like to have an additional approach that distinguishes "unknown variant" from "new known variant", so that code with a catch-all match arm still requires fixing when adding a new *known* variant. Open enums aren't necessarily non_exhaustive enums. The RFC talks about how they address different things (API vs ABI), but sometimes I might want to be compatible with ABI (e.g. for FFI) but still have Rust catch issues for me with *API*.
We've talked about this before, some kind of *named* `CatchAll(Int)` variant, so that matches handle the CatchAll but have to be updated if there's a new named variant that isn't `CatchAll`. I see that this is mentioned in future work, so, please consider this enthusiastic support for that future work ("Discriminant ranges for named variants").
This is *not* a blocker for *this* proposal; it's a desire for an additional future extension that I think naturally fits in with this.
> To declare an unnamed variant, the enum must have an explicit repr(Int). Int is one of the primitive integers or C. If it is C, then Int below is isize.
This would be a change, as `repr(C)` is normally "whatever size C would choose", which may be smaller.
TIL that [`cfi_encoding`](https://doc.rust-lang.org/nightly/unstable-book/language-features/cfi-encoding.html) exists; that's really helpful.
### Tyler
I like this feature; it fills two big gaps in the language.
I really like that this gives us a way to safely use `repr(C)` enums with C enums. That feels like a big gap in our interop story.
I also love that it gives us an ABI-compatibility story for enums. ABI stability is a huge topic and not something that is easily solved all at once (the notable exception was Swift which is controlled by one company, and that was clearly a huge endeavor even for them). For Rust I believe we should look for opportunities to chip away at it one piece at a time, and this feature would be a big piece.
I see the purpose of enums as representing a "set of named variants". This extends that to "a set of named variants, some of which are unknown".
On the note about `as` casts: We have `as` casts for enums today. I agree we should not block an orthogonal feature on a yet-to-be-designed alternative. I'm not convinced that they are bad in a lossless setting like this one, but it's a separate discussion in any case.
Finally, this is a really thorough document; thank you for all the work that went into it.
### nikomatsakis
(Not present.)
### scottmcm
(Not present.)
### TC
Thanks for the careful document. It's clear a lot of thought and consideration has gone into this.
I'm sympathetic to the motivation. It solves problems. I could see us doing this. I'm also sympathic to scottmcm's concerns around the purpose of enums and would like to hear more about that.
Separately, there are a lot of details in this RFC to review. E.g., I'm not sure about the proposed behavior of giving higher precedence to named variants and allowing them to put holes in the ranges of the unnamed ones implicitly. I might expect that we'd want that done explicitly.
### Jack
I like this - I think others have thoughts on this with more nuance than I would have.
### Waffle
A lot of the motivation examples for "why not newtyped integer" feel weak to me. The only things from there that seem interesting (to me) are the tool compatibility:
> - Code analysis and lints specific to enums are unavailable.
> - No "fill match arms" in rust-analyzer.
> - The non-exhaustive patterns error lists only integer values, and cannot suggest the named variants of the enum.
> - The unstable non_exhaustive_omitted_patterns lint has no easy way to work with this enum-alike, even though treating it like a non_exhaustive enum would be more helpful.
> - Generated rustdoc is less clear (the pseudo-enum is grouped with structs).
----
> - `start..` (`core::ops::RangeFrom<Int>`)
> - Equivalent to `start..=Int::MAX` for non-`repr(C)` enums.
~~What is it for `repr(C)` enums?...~~
Explained in a later section: https://github.com/kupiakos/rs-rfcs/blob/e1f2b9158b29b2868c65eb18c8356c38b33eb8ae/text/3894-unnamed-variants.md#reprc-behavior; Feels like the behavior is the same though, just that `Int` is chosen based on the `repr(C)` rules.
---
I think I'm generally in favour of this though :3
***
### Nadri (not on the call)
I see a compelling story there where this is a natural extension of `#[non_exhaustive]`: not only is this reserving space for future variants, but controlling the ranges they may take.
The natural next step I'd want would be for
```rust
enum Foo {
A,
B,
_
}
```
to be an alternative syntax for `#[non_exhaustive]`. Then the feature falls naturally out of that.
Two related thoughts:
- this has overlap with pattern types, tho I don't think we'd be justified in waiting for that;
- there's a general underlying story of "precise control over type layout" that I'd like to see a principled solution for. Stuff like saying "read a byte here, then depending on that value you get this variant or that variant". https://inria.hal.science/hal-04165615/document is the extreme version of such control, and MiniRust's [`Discriminator`](https://github.com/minirust/minirust/blob/32c79e4b233ea4d60c87737a2f72455c341a40b1/spec/lang/types.md?plain=1#L113) describes probably the most advanced we'd want to have in Rust.
Again, this is for further thoughts and not particularly blocking here.
## Author's questions for the team
* Should something like this exist? Do we agree that this is a problem to solve?
* Do you think any of the alternatives are better than unnamed variants? Main alternatives:
* `struct` is the only way to represent an open enum
* `..` at the end
* `Other = ..` variant
* `Other(Int)` variant with zero-cost optimization using a pattern type
* Is there any major alternative I haven't listed?
* Do you feel comfortable checking a box on this RFC?
---
TC: Thanks Alyssa again for the document. We now turn to you. What are your thoughts on our vibes? What do you see as the best things to discuss to help move this forward? Since scottmcm isn't here, maybe talk a bit about what you and he have discussed and the state of that concern.
Alyssa: Scott's primary pushback was that it was a philosophical disagreement about what makes a struct or an enum. I argued that if the purpose of a type is to name a set of value, then that's better to write as an enum, even if it could be written as a struct. We seemed to come to some understanding, even if not 100%. He wanted me to expand on one of the alternatives and on why I didn't think it was the ideal solution.
Jack: That alternative is the wildcard discriminant with an unknown variant field?
Alyssa: Yes
Josh: Either we want an unknown variant that looks like a field representing the discriminant or an unknown variant that sets what the discriminant could be and you ask what the discriminant is by calling `.#discriminant`. We don't want both of those but part of my vibe check is to have one of those.
Jack: This idea that there's an unmaeable variant with multiple values could in theory be extended to regular enums. That a named variant could have multiple values. I'd expect you could have one varient that's a range 1..10 and another variant that's 1..20. That seems like a natural exttension of this.
Alyssa: I think I have this in the future possibilities that we could implemnet this. But this RFC was already too large. But it's ambiguous what value should be chosen.
Jack: My feeling is that it's arbitrary. The compiler can choose what values to use. Or you can have an attribute that sets this.
Jack: Thinking about this question might help us answer the fundamental question of "what are enums?". And if we extend enums we have today to also allow any named or unnamed variants to allow ranges of values, then the unnamed variant would just be another kind of variant that doesn't do anything new. That may be what Scott wants.
Alyssa: Yes.
Tyler: We talked about Scott having reservations, Alyssa talked to Scott. But I feel negatively about trying to hold out room for big reservations that haven't been expressed at this point.
Tyler: The only alternative I see is whether the field is embedded in the variant that's embedded in the discriminant value or whether it is the discriminant value. We can talk about that but I want to see if taht's the only alternative.
Alyssa: The 2 popular enum alternatives are "named variants representing range" or have the variant be a tuple struct to represent an unnamed variant.
Tyler: The tuple struct is the one I was thinking.
Alyssa: I do think un-named varients simplify the space.
Josh: I agree with that. It makes sense to have unknown variants with `..` etc. But a future work could also have named variants with ranges, pull out those discriminants and look at those. That's useful and I'd immediately use that, but it wouldn't block this proposal.
Josh: Even in the case of pattern types, I think we'd have the ability to say "I know which variation of which enum it is because I just matched on it so I should be able to ask which discriminant it is". That would be useful and might remove some of the "pattern types instead" rather than "pattern types in addition" discussion.
Alyssa: Was there any major alternative I haven't listed?
## CFI (Control Flow Integrity)
TC: Could you talk about the CFI story?
Alyssa: CFI doesn't follow the C standard. It adds an additional constraint: when you call an indirect function (e.g. with a function pointer) with CFI, it says it compares the types of the parameters (the caller and callee function signatures).
Alyssa: When you compile a Rust enum today, it compiles it with the C enum. When you have a `repr(transparent)` struct, it has a different CFI encoding and it's different from a C enum. But it matches a C typedef. If you have a struct in Rust and typedef in C, that's fine. If you have an enum in Rust and enum in C that's fine. If you have a mismatch, you have to handle that.
## ABI evolution shiny future
TC: Maybe talk about the future direction. Is the ultimate goal to support dynamic library ABI evolution? How does that relate to open fielded enums?
Alyssa: That's definitely a goal of this. I want to be able to as a library author prepare for a future where I define a fielded enum that's forward compatible so I can upgrade and downgrade the dynamically linked library without it being a breaking change.
Alyssa: In order to have an ABI-stable Rust enum, you can't use a Rust enum, you have to emulate it unsafely.
## Field access syntax for discriminants
Frank: Regarding the idea \[in the “Forward compatibility with newtype structs” section] of
> .0 provides direct access to the discriminant value of enums with an explicit representation:
this seems problematic to me for enums (with tuple-struct variants) *with fields* because one would usually assume rather to access that field in this case, not the discriminant.
Nadri: there are also other proposals (https://github.com/rust-lang/rfcs/pull/3607) that want to name the discriminant as a field, and using `.0` feels wrong for that.
Alyssa: That's fair but it is unambiguous. You have to match on the enums to access the field otherwise.
Frank: Depends on what other future language features could interact with this.
Alyssa: I don't expect this to land but it would make the forward compatibility better
Frank: What is the motivation for forward compatibility?
Alyssa: People are using typedefs for this and they'd like to use enums.
## Next steps?
Josh: What's the next step? Lang experiment? Review the RFC?
Alyssa: Does anyone feel very uncomfortable checking a box on the RFC?
TC: It's a long RFC; I feel a responsibility to review it thoroughly. So I would not check a box today, but I don't necessarily have any fundamental concerns.
Frank: If the statement of the RFC is to decide for this and not the alternatives, I find many of the alternatives preferrable to the main proposal. In my Haskell experience ADTs feel more closer to enums and I mapping them to C isn't necessarily on my mind. Rust enums do have a lot of properties that are different and this proposal would weaken them and it would be more difficult to teach them.
Alyssa: My goal was to declare all the values upfront. That's what an ADT does. You just reserve a whole bunch of values without a name.
Mark: It feels to me there are parts of this that are separateble. E.g. the `as` cast could be defferred. I'm curious if there are some aspects that could be pushed forward faster than the whole thing. It is large with lots of inter-related pieces. But broadly I'm happy.
TC: Is there any implementation work that's been done?
Alyssa: I've worked on an implementation on my own branch but I haven't published it.
TC: Would it be helpful for us to declare this as a lang experiment, create a tracking issue, etc., so that you can start working with a reviewer to land some of this in the compiler?
Alyssa: I could see that. I'm not super familiar with teh lang experiment process. Does it allow experiments to land before the RFC is merged?
TC: That's correct.
Alyssa: Regarding `as` casts, I don't think they're the enemy in general. Only when they introduce loss. But I understand the push back. But right now the only alternative is `unsafe` and that's worse than an as cast.
Tyler: +1
Josh: +1
Frank: How important is having library-specified ranges vs. just saying "I want all integers"? We could have this specified with an attribute. That would let us do fewer language changes.
Alyssa: That's an alternative that's been listed. It's not super common to only require reserving a subset of integer rather than all of them. But there are some users that do this. E.g. TockOS. There are workarounds but they are a pain.
Alyssa: I think a lang experiemnt would accelerate landing this and I've started already. Is there any drawback to that?
TC: The idea of a lang experiment is that by landing some implementation work that can often help to answer questions, bulid confidence in the design, and uncover design problems. The main drawback is for you, as the person pushing this forward, that we may change our mind. The work is speculative. Some of it may end up being thrown away.
Josh: An experiment is useful when we feel we're 90% there. I don't know whether we're there right now.
Tyler: What questions do we want an experiment to answer? Alyssa it sounds you've already implemented it expecimentally.
Alyssa: Not all of it, just major parts. Not the lints, I haven'1t implemented repr(C) at all.
Tyler: Normally I'd go for an experiment where an RFC is missing a lot of details. In this case I feel the RFC explores a lot of the details. Do people feel that ean experiment will help?
TC: My biggest worry is the size. I have confidence-building concerns. It's difficult to build confidence in all of these details. I wonder whether an experiment might help find where we could break this down a bit and take it in smaler pieces. Then, maybe we can move those more quickly toward stabilization. An antipattern for us is accepting an RFC and large parts of it not stabilizing for years. I worry this could happen here.
Alyssa: I'd argue the RFC is large because the alternatives are really exansive.
Scott: One part of doing the experiment would be to check how these things translate to MIR and what their semantics are. If we make the underscore thing a variant then might run into issues. What exactly is the lowering details.
Alyssa: My initial thought is an equivalent to a desugar where you name each veriant that names every variant.
Scott: I don't know what the implicatieons of using that desugaring would be. What does that mean for things like how the compiler does "what's the discriminant for that enum", how that's decoded, how this fits into the pieces. I wonder if even if this is the right syntax, it should expand to something that's not an enum. And an experiment can show that.
Alyssa: I can't foresee an issue with it but that seems like a good place for a lang experiment to see if I'm wrong.
Frank: I'm comparing to `Option<NonZero<integer>>` where all possible integers are used in memory and `mem::discriminant` handles that. And both options could be considered reasonable here.
Scott: I also think that would be nicer, but it means `mem::discriminant` will cause adding a discriminant to be a breaking change.
```rust
enum Foo {
_ = ..
}
// after
enum Foo {
A = 0,
_ = ..
}
// Does adding the `A` change what this does,
// and how does that impact what it means to be a different `VariantIdx`
// and what does that mean for the semantics of `Rvalue::Discriminant` in MIR
mem::discriminant(&transmute::<_, Foo>(1)) == mem::discriminant(&transmute::<_, Foo>(0))
```
Frank: I was thinking of the world in which the `enum` otherwise only offers the same kind of capabilities
that a `#[non_exhaustive]` fieldless enum currently does.
Scott: In the world where the underscore was a single variant, does adding an `A` variant change what `mem::discriminant` does?
Alyssa: No, it doesn't change that.
## Discriminant value overrides
TC: On the skim today, something that stood out was the idea that named variant discriminant values would poke little holes in the ranges stated for unnamed variants. I would expect that the explicit ranges of the unnamed variants would be treated literally and that overlap would be an error. I know this would not be as pretty or as ergonomic. But it's what I'd expect of Rust here.
Alyssa: I had thought about doing it this way — it's mentioned in the RFC — but had gone the other way for the ergonomic reasons. We added lints to help with this.
TC: We could always relax this later — if we found that the ergonomics really were a problem — but allowing this is a one-way door. To make this easier to stabilize, I'd suggest finding places such as this where the more conservative thing can be done initially. In this case, that would also cut scope by then avoiding the lints.
Alyssa: Makes sense.
## Experiment?
TC: Scott, do you think it'd be valuable to do an experiment here? Do you want to work with Alyssa on this?
Scott: I'm torn on how much it will affect the surface syntax.
TC, scottmcm: We can treat this experiment as approved, if the author finds value in it.
(The meeting ended here.)
---
## Drawbacks of pattern types?
Nadri: I didn't see "wait for pattern types" in the Alternatives. What's the argument against that?
Nadri: For completeness, what I have in mind is that if/when we get pattern types, the motivating example could be written as:
```rust
#[repr(bikeshed_my_whole_type_really_is(u32))]
enum Fruit {
Apple, // Apple is represented with 0u32.
Orange, // Orange is represented with 1u32.
Banana = 4, // Banana is represented with 4u32.
Other(u32 is 3 | 5..=10),
}
```
To make this nicer, I'd imagine a `repr(bikeshed_my_whole_type_really_is(u32))` that forces the type to fit into `u32` and errors if impossible. This would allow casts to `u32` unconditionally and casts to `u32` if the enum covers everything.
Drawbacks I see:
- If you want an "everything else" variant you have to specify the range explicitly;
- We have to be explicit about ranges generally: can't write `3..=10` above or you'll get an error
Alyssa: The alternative you're looking for is `A "wildcard" tuple variant with an unknown discriminant field`. It could be renamed to something that more explicitly mentions pattern types: I generally named the shape of the alternative.
Nadri: I see, thanks!
Josh: I like that concept very much, but also don't think it should be a blocker. I do very much want this possibility, but I would ideally like this possibility *without* waiting for pattern types. That would mean that handling for the unknown variant would have to treat its argument as potentially any value for the discriminant type, rather than limited values, but that seems okay.
Alyssa: what happens if you `&mut` the tuple field in `Other`? It's a pattern type now so that can be sound.
Josh: We could, potentially, have a more limited "you can't take a `&mut` of the field". Or, more likely, we handle it by letting you get access to the discriminant, which we already likely won't have a way to get as `&mut`. e.g. `Other = ..` and then get `.#discriminant` if you want the value.
Nadri: I'm generally biased against "we need a feature now so let's get a less-good feature" (assuming it's less-good ofc). Alyssa: `&mut` is exactly where pattern types really shine: you'd get a `&mut (u32 is 3 | 5..)` which does allow writing to it if you can prove the integer you're writing is in range.
Nadri: ah, if the pattern type is explicitly a discriminant (and thus can end up in a niche), things might get weird.
Frank: I kinda had this exact same thought as Nadri, before having read this even (and before seeing the relation to pattern-types mention in the RFC either), so I would have raised exactly the same question, just with the added details that it’d probably also want to come with some feature for *private fields* in enums, and then make the field of `Other` private. That would then also prevent any external users from creating any `&mut` reference to the field, either.
Nadri: the argument against the pattern types approach I find most compelling is "The wildcard variant optimization shown here has no clear way to extend to enums with fields in the future.". I do think the right approach for that is to go all-in with fine layout control, but that's even farther out of reach
Nadri: the other argument I am swayed by is the need for private variants
Nadri: I guess my point is less "maybe pattern types do this better" but rather "assuming that we're likely to get pattern types, and given that they can express a large chunk of this feature, would we still want this feature"
## Josh vibe check responses
> We're trying to move *away* from `as` casts. I understand what this is trying to do, but I hope we can do it in another way that doesn't involve `as`.
Alyssa: See the section "Don't introduce a new `as` cast" for rationale
Josh: :+1:, not a blocker, just endorsing the future work. :)
> I would like to have an additional approach that distinguishes "unknown variant" from "new known variant", so that code with a catch-all match arm still requires fixing when adding a new *known* variant. Open enums aren't necessarily non_exhaustive enums.
I believe the `IsNamedVariant` derive macro and `deny(non_exhaustive_omitted_patterns)` would handle that perfectly - include a wildcard branch with an `if` guard. Should `IsNamedVariant` be an implemented part of the RFC rather than an alternative/future work?
`IsNamedVariant` would give a runtime function, and that's not what I mean. I'm looking to add a new known variant, and have the compiler tell me all the matches and similar that I need to update, just as I do today with exhaustive enums. I want an open enum for FFI compatibility, not for API/semver compatibility. (It might even be a *private* enum, which I'm only using in my FFI -sys crate, but I want to see everywhere in my crate that needs updating.) This isn't the same as the lint about `non_exhaustive` enums; I'd like the behavior currently available from *exhaustive* variants.
> IIRC this would be a change, as `repr(C)` is normally "whatever size C would choose", which may be smaller?
I struggled to write this section clearly - the below `repr(C)` section should indicate that it's not a change compared to current behavior. Ideas to improve the language are welcome.
> I don't think that `non_exhaustive` should conflict with this.
I waffled quite a lot on that! See the "Require non_exhaustive, don't forbid it" section for rationale - though do you mean to say it should work with or without?
> We've talked about this before, some kind of *named* `CatchAll(Int)` variant, so that matches handle the CatchAll but have to be updated if there's a new named variant that isn't `CatchAll`.
See the alternative `A "wildcard" tuple variant with an unknown discriminant field` - it's best used with a pattern type
## Waffle vibe check responses
> A lot of the motivation examples for "why not newtyped integer" feel weak to me. The only things from there that seem interesting (to me) are the tool compatibility:
Alyssa: If our solution for open enums does not include the `enum` keyword, they will always be second-class to closed enums.
Even with tooling improvements to newtyped integers, it is still a much worse experience to read and write.
I want our interop story to be better than that.
> What is it for `repr(C)` enums?...
The below section dives into the "fun" behavior of `repr(C)` as it exists and how it would be affected. I welcome improvements in the reference-style explanation to avoid exceptions like that
> `RangeFrom` mention seems a bit irrelevant
My goal in indicating that is to highlight that the expression may be a `const` of that type, and does not have to use the actual `start..` syntax.
---
Alyssa: My worry with this would treat them as second class
Waffle: my read is that it could be fine for them to be second class
## TC vibe check responses
Alyssa to TC: see "Forbid unnamed variants' discriminants from overlapping named ones" for why unnamed variants take precedence over named variants: primarily for user ergonomics when adding variants in the middle of an unnamed range.
## (minor) Interaction with `non_exhaustive`
> An enum declared both non_exhaustive and with an unnamed variant is rejected
tmandry: This might go too far. I'd suggest making it a lint, because `#[non_exhaustive]` is purely redundant in an enum with an unnamed variant.
Alyssa: That's fair! Anybody disagree?