Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Layout of single-variant enums #79

Open
nikomatsakis opened this issue Jan 10, 2019 · 13 comments
Open

Layout of single-variant enums #79

nikomatsakis opened this issue Jan 10, 2019 · 13 comments
Labels
A-layout Topic: Related to data structure layout (`#[repr]`) C-open-question Category: An open question that we should revisit S-not-opsem Despite being in this repo, this is not primarily a T-opsem question T-lang

Comments

@nikomatsakis
Copy link
Contributor

Enums that contain a single variant and which do not have an
explicit #[repr] annotation are an important special case. Since
there is only a single variant, the enum must be instantiated with
that variant, which means that the enum is in fact equivalent to a
struct. The question then is to what extent we should guarantee
that the two share an equivalent layout, and also how to define the
interaction with uninhabited types.

As presently implemented, the compiler will use the same layout for
structs and for single variant enums (as long as they do not have a
#[repr] annotation that overrides that choice). So, for example, the
struct SomeStruct and the enum SomeEnum would have an equivalent
layout (playground)::

struct SomeStruct;
enum SomeEnum {
  SomeVariant,
}

Similarly, the struct SomeStruct and the enum SomeVariant in this
example would also be equivalent in their layout
(playground):

struct SomeStruct { x: u32 }
enum SomeEnum {
  SomeVariant { x: u32 },
}

In fact, the compiler will use this optimized layout even for enums
that define multiple variants, as long as all but one of the variants
is uninhabited
(playground):

struct SomeStruct { x: u32 }
enum SomeEnum {
  SomeVariant { x: u32 },
  UninhabitedVariant { y: Void },
}
@nikomatsakis nikomatsakis added A-layout Topic: Related to data structure layout (`#[repr]`) C-open-question Category: An open question that we should revisit labels Jan 10, 2019
gnzlbg added a commit to gnzlbg/unsafe-code-guidelines that referenced this issue Jul 8, 2019
gnzlbg added a commit to gnzlbg/unsafe-code-guidelines that referenced this issue Jul 8, 2019
gnzlbg added a commit to gnzlbg/unsafe-code-guidelines that referenced this issue Aug 15, 2019
@gnzlbg
Copy link
Contributor

gnzlbg commented Aug 15, 2019

In fact, the compiler will use this optimized layout even for enums
that define multiple variants, as long as all but one of the variants
is uninhabited

We could resolve this with:


The layout of multi-variant enums with one inhabited variant is the same
as that of the single-variant enum containing that same inhabited variant.

For example, here:

struct SomeStruct { x: u32 }
enum SingleVariantDataCarrying {
    DataCarryingVariant(SomeStruct),
}
enum Void0 {}
enum Void1 {}
enum MultiVariantSingleInhabited {
  DataCarryingVariant(SomeStruct),
  Uninhabited0(Void0),
  Uninhabited1(Void1),
}

the layout of MultiVariantSingleInhabited is the same layout as that of
SingleVariantDataCarrying.


but doing so is blocked on resolving the layout of uninhabited types: #165 .

gnzlbg added a commit to gnzlbg/unsafe-code-guidelines that referenced this issue Aug 15, 2019
@hanna-kruppe
Copy link

That's not what is currently (or is planned to be) implemented, again because uninhabited types aren't all ZSTs and can be partially constructed. The plan of record is to only omit variants which are uninhabited and ZSTs.

@gnzlbg
Copy link
Contributor

gnzlbg commented Aug 15, 2019

@rkruppe Makes sense. I think this might be then covered with a guarantee about layout for unions with a single (inhabited?) non-zero-sized field.

@RalfJung
Copy link
Member

RalfJung commented Aug 16, 2019

@rkruppe those issues apply to structs though, not to enums. Surface Rust does not allow partial initialization of enums. But maybe MIR does... in the spirit of rust-lang/rust#63616, I wonder what happens with something like

let x: Result<u32, (u32, !)> = Err((42, panic!()));

@hanna-kruppe
Copy link

Yes, that will have partial initialization in MIR. "Surface Rust allows partial initialization" was always just part of the story (and is even ruled out again right now!), the MIR-level motivation, specifically enabling aggressive move forwarding/emplacement without having to special case siblings of uninhabited places (to introduce fake temporary destinations), applies universally to structs, enums, and every other aggregate.

@heaths
Copy link

heaths commented Sep 27, 2022

Seems there was some discussion in 2020 about univariant enums. What I cant find is if there was ever a discussion - or, better, a decision - on supporting #[repr(transparent)] on bivariant enums with an empty variant like Result<(), u32> or similar to cover FFI-compatible scenarios. @gnzlbg I see your moniker related to a lot of this discussion. Do you know? From the aforementioned issue, I didn't see any follow-up since 2019 about an RFC. Is that still needed?

@scottmcm
Copy link
Member

@heaths It's unclear to me what you expect transparent on a multi-variant enum like Result<(), u32> is supposed to mean -- that type is 64 bits as it needs a discriminant and the payload. Did you mean something where one of the arms is impossible, like Result<u32, !>?

@heaths
Copy link

heaths commented Sep 27, 2022

I see your point. Perhaps this is overly optimizing for a specific use case - FFI - but since my (and others') intention was to define native functions returning non-zero errors, would constraints make it easier to discriminate e.g. Result<(), std::num::NonZeroU32>?

@scottmcm
Copy link
Member

@heaths That's a niche optimization question, so you're probably looking for a different issue -- it's definitely not a "single-variant enum", even if it might layout-optimize down to 32 bits.

@heaths
Copy link

heaths commented Sep 27, 2022

I ran across rust-lang/rust#84277 as well, which eventually - hopefully - is a way to solve this seemingly common FFI scenario. I agree it's a niche optimization, though.

@scottmcm
Copy link
Member

@heaths Yes, that exact FFI scenario is written out specifically in the RFC: https://rust-lang.github.io/rfcs/3058-try-trait-v2.html#implementing-try-for-a-non-generic-type

@digama0
Copy link

digama0 commented Sep 27, 2022

@heaths By the way, from this exchange I think there might have been a miscommunication: @scottmcm means it's a (niche optimization) question, not a niche (optimization question). That is, it's a question about niche-optimization (that is, efficient bit-packing of enums where the variants disallow some range of values), not a question about optimizations which are "niche" i.e. applicable only to a restricted set of scenarios.

@pnkfelix
Copy link
Member

pnkfelix commented Jun 13, 2023

Discussed at T-opsem backlog bonanza.

This is T-lang, not opsem

@rustbot label: +T-lang +S-not-opsem

@rustbot rustbot added T-lang S-not-opsem Despite being in this repo, this is not primarily a T-opsem question labels Jun 13, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-layout Topic: Related to data structure layout (`#[repr]`) C-open-question Category: An open question that we should revisit S-not-opsem Despite being in this repo, this is not primarily a T-opsem question T-lang
Projects
None yet
Development

No branches or pull requests

9 participants