Skip to content

Fix field-less repr(C) enum docs - #2018

Merged
traviscross merged 12 commits into
rust-lang:masterfrom
RalfJung:repr-c-enum
Aug 25, 2026
Merged

Fix field-less repr(C) enum docs#2018
traviscross merged 12 commits into
rust-lang:masterfrom
RalfJung:repr-c-enum

Conversation

@RalfJung

Copy link
Copy Markdown
Member

The docs for field-less repr(C) enums are wrong in the sense that they say "the C representation has the size and alignment of the default enum size and alignment for the target platform's C ABI" which implies that there's a single size and alignment (determined by the target) that all repr(C) enums share -- which isn't true. The size of the enum depends on the discriminant values and is intended to mimic what the default C compiler for the target would do with an enum that has the same discriminant values.

Also, it seems worth mentioning the type that the discriminant expressions of an enum are type-checked at: that's isize for all enums expect those with primitive representation.

This PR presupposes that we are going ahead with rust-lang/rust#147017 and documents things as-if the FCW added there was already a hard error. This is mostly because otherwise it's unclear what we should document as the logic before that bug doesn't always match the target's C compiler (see rust-lang/rust#146504).

@rustbotrustbot added the S-waiting-on-review Status: The marked PR is awaiting review from a maintainer label Sep 25, 2025
Comment threadsrc/type-layout.md Outdated

> [!NOTE]
> The enum representation in C is implementation defined, so this is really a "best guess". In particular, this may be incorrect when the C code of interest is compiled with certain flags.
> For maximum portability, it is always preferred to set the size and alignment explicitly using a [primitive representation](#r-layout.repr.primitive.enum).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the Rust reference but here we are explicitly steering people on how to do an FFI portability thing primarily designed to be compatible with C code. I think we might as well go ahead and note how this can be done in the C source code. Please feel free to disagree, I don't want to scope creep anything unduly.

Suggested change
> For maximum portability, it is always preferred to set the size and alignment explicitly using a [primitive representation](#r-layout.repr.primitive.enum).
> For maximum portability, it is always preferred to set the size and alignment explicitly using a [primitive representation](#r-layout.repr.primitive.enum).
> This can be mimicked in C23 or C++17 using the syntax for enums with fixed underlying types.

@rustbot

This comment has been minimized.

@RalfJung

Copy link
Copy Markdown
MemberAuthor

@traviscross is there anything this needs to proceed? :)

@traviscrosstraviscross added the I-lang-docs-nominated Nominated for discussion during a lang-docs team meeting. label Jan 19, 2026
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

Comment threadsrc/type-layout.md Outdated
#### `#[repr(C)]` Field-less Enums

For [field-less enums], the `C` representation has the size and alignment of the default `enum` size and alignment for the target platform's C ABI.
For [field-less enums], the `C` representation requires the discriminant values to either all be representable by the `int` type in the target platform's C ABI, or to all be representable by the `unsigned int` type. Nevertheless, the type of the discriminant is `isize`. The size and alignment of the enum then match that of a C enum with the same discriminant values (and without a fixed underlying type). Crucially, the equivalent C type is determined based on the discriminant values *after* they have been cast to `isize`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were looking at this paragraph, and we were feeling like this may be packing too many things into a single rule. We were wondering about maybe splitting it up into a few separate things, perhaps along these lines:

Change the items.enum.discriminant.repr-rust rule to also cover repr(C). It could maybe look something like this:

r[items.enum.discriminant.repr-discriminant]

Enums with the [Rust representation] or [C representation] have discriminants of type isize.

With the [Rust representation], the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout.


And then in the layout section:

r[layout.repr.c.enum.discriminant]

For [field-less enums] with the C representation, all discriminant values (which are of [type isize][items.enum.discriminant.repr-discriminant]) must be representable by either the int or unsigned int type in the target platform's C ABI.

r[layout.repr.c.enum.size-align]

A [field-less enum] with the C representation will have the same size and alignment as a C enum that has the same discriminant values and does not explicitly specify the underlying integer type.


WDYT?

@RalfJungRalfJungMar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the [Rust representation], the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout.

This is also true with the C representation -- the memory layout will usually be i32, not isize, and may be even smaller (e.g. on ARM). The only difference is that with the Rust representation we leave it unspecified which layout we pick (today we pick the smallest that can fit all values), while with the C representation we pick the same layout as a C compiler would.

The rest sounds good. However here...

A [field-less enum] with the C representation will have the same size and alignment as a C enum that has the same discriminant values and does not explicitly specify the underlying integer type.

... it may be worth mentioning (possibly as a non-normative note) that this is about the values after they have been cast to isize. The order of operations is:

  • Compute all discriminant values. This produces isize result, so things get wrapped if they don't fit.
  • Check that they all fit into int or all fit into unsigned int.
  • Compute the size the C enum with those (already wrapped!) values would have.

@RalfJungRalfJungMar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, also, this...

all discriminant values (which are of [type isize][items.enum.discriminant.repr-discriminant]) must be representable by either the int or unsigned int type in the target platform's C ABI.

...is subtly wrong. The correct requirement is that either all values fit in int or all values fit in unsigned int. This is not equivalent to what you wrote: if one value is -1 and another one is 0xFFFFFFFF, then they both "fit into int or unsigned int", but the enum still gets rejected because they don't all fit into int, and they also don't all fit into unsigned int. I specifically worded this very carefully in my PR. :)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I see now.

Also, just to double check, I was uncertain if the "without a fixed underlying type" is referring specifically to the new C23 enum name: type {...} syntax, or something else (__attribute__((__packed__))? -fshort-enums?). I could have sworn this ability existed before C23, but my memory is clearly clouded.

And...Would you be OK if I push these changes on top of your PR here?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the "without a fixed underlying type" is referring specifically to the new C23 enum name: type {...} syntax

That is what I meant, yes. AFAIK that is the only tweak that the standard has here. GCC and other compilers have their extensions but if we start talking about all compiler extensions here we'll never be done. ;)

And...Would you be OK if I push these changes on top of your PR here?

Yes, that's entirely fine. :)

@traviscrosstraviscross changed the title fix field-less repr(C) enum docsfix field-less repr(C) enum docsApr 28, 2026
@RalfJung

Copy link
Copy Markdown
MemberAuthor

@traviscross@ehuss what is the current status of this? I think I answered all questions above.

@rustbot

This comment has been minimized.

@rustbot

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@traviscross

Copy link
Copy Markdown
Contributor

Thanks @RalfJung for the ping. I pushed a set of commits addressing the items that were raised in discussion earlier. Please let me know if this looks right to you.

@RalfJungRalfJung left a comment

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, thanks. :) Two minor nits for the non-normative part.

View changes since this review

Comment threadsrc/items/enumerations.md
Comment threadsrc/type-layout.md Outdated
The revised `layout.repr.c.enum` rule was doing a bit too much. Let's
split it up.
We're stating a rule about discriminant values here that `rustc` does
not enforce today other than with an FCW; let's make a note of that.
There's already a link reference definition for "primitive
representation", so let's use that.
The portability note for enumerations was in the passive voice.
Advice is best given in the imperative mood, so let's adjust it.
While here, let's drop an unneeded comma.
We tell the reader to use a fixed underlying type in C. Let's mention
the revision of C in which this was introduced.
We had split where we were defining the type for `enum` discriminants
between the enumerations chapter and the layout chapter. Let's fully
define this within the enumerations chapter.
Let's add examples to make clear what it means for the discriminant to
have a type. This makes a "for example" sentence redundant, so let's
drop that.
Let's add examples for the rules about the size and alignment of
certain `enum`s.
In the `items.enum.discriminant.type` rule, we had a clause in
parentheses that isn't parenthetical, as without it, the rule would be
wrong. Let's remove these parentheses.
@traviscrosstraviscross removed S-waiting-on-review Status: The marked PR is awaiting review from a maintainer I-lang-docs-nominated Nominated for discussion during a lang-docs team meeting. labels Aug 25, 2026
@traviscross

Copy link
Copy Markdown
Contributor

Thanks @RalfJung.

@traviscross
traviscross added this pull request to the merge queueAug 25, 2026
@traviscrosstraviscross changed the title fix field-less repr(C) enum docsFix field-less repr(C) enum docsAug 25, 2026
Merged via the queue into rust-lang:master with commit a6102f9Aug 25, 2026
7 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@RalfJung@rustbot@traviscross@ehuss@workingjubilee