From 57360f2ce1ea1a1622c8543835bc5e1eec6dcf72 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Thu, 25 Sep 2025 12:25:04 +0200 Subject: [PATCH 01/12] fix field-less repr(C) enum docs --- src/type-layout.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/type-layout.md b/src/type-layout.md index e898711a85..4ac0e71004 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -367,10 +367,12 @@ assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0); r[layout.repr.c.enum] #### `#[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 be representable by the `int` type in the target platform's C ABI. 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). > [!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) on the Rust side, and a fixed underlying type on the C side. > [!WARNING] > There are crucial differences between an `enum` in the C language and Rust's [field-less enums] with this representation. An `enum` in C is mostly a `typedef` plus some named constants; in other words, an object of an `enum` type can hold any integer value. For example, this is often used for bitflags in `C`. In contrast, Rust’s [field-less enums] can only legally hold the discriminant values, everything else is [undefined behavior]. Therefore, using a field-less enum in FFI to model a C `enum` is often wrong. @@ -451,7 +453,7 @@ Primitive representations can only be applied to enumerations and have different r[layout.repr.primitive.enum] #### Primitive representation of field-less enums -For [field-less enums], primitive representations set the size and alignment to be the same as the primitive type of the same name. For example, a field-less enum with a `u8` representation can only have discriminants between 0 and 255 inclusive. +For [field-less enums], primitive representations set the type of the discriminants to the primitive type of the same name. Furthermore, the enum's size and alignment are guaranteed to match that type. For example, a field-less enum with a `u8` representation has discriminants of type `u8` and hence can only have discriminants between 0 and 255 inclusive. r[layout.repr.primitive.adt] #### Primitive representation of enums with fields From 0e004a66d04da2bf9475f1c8eaf21e7c36e96cc3 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 3 Oct 2025 13:43:56 +0200 Subject: [PATCH 02/12] also allow enums where all discriminant values fit an unsigned int --- src/type-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type-layout.md b/src/type-layout.md index 4ac0e71004..acb96947e5 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -367,7 +367,7 @@ assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0); r[layout.repr.c.enum] #### `#[repr(C)]` Field-less Enums -For [field-less enums], the `C` representation requires the discriminant values to be representable by the `int` type in the target platform's C ABI. 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). +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). > [!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. From 40ca6c4804afdf95552838910857b49987b23d2e Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 3 Oct 2025 14:01:47 +0200 Subject: [PATCH 03/12] emphasize that the isize values matter --- src/type-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type-layout.md b/src/type-layout.md index acb96947e5..3d65e3f19e 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -367,7 +367,7 @@ assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0); r[layout.repr.c.enum] #### `#[repr(C)]` Field-less Enums -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). +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`. > [!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. From 4e4a6ab220855fa2537400483581cb03837002b0 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 22 Aug 2026 14:30:46 +0000 Subject: [PATCH 04/12] Refactor fieldless `repr(C)` enum rules The revised `layout.repr.c.enum` rule was doing a bit too much. Let's split it up. --- src/items/enumerations.md | 5 ++--- src/type-layout.md | 10 +++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/items/enumerations.md b/src/items/enumerations.md index 5cb4000bcf..80a4244e28 100644 --- a/src/items/enumerations.md +++ b/src/items/enumerations.md @@ -114,8 +114,8 @@ r[items.enum.discriminant] r[items.enum.discriminant.intro] Each enum instance has a _discriminant_: an integer logically associated to it that is used to determine which variant it holds. -r[items.enum.discriminant.repr-rust] -Under the [`Rust` representation], the discriminant is interpreted as an `isize` value. However, the compiler is allowed to use a smaller type (or another means of distinguishing variants) in its actual memory layout. +r[items.enum.discriminant.type] +Enums without a [primitive representation] have discriminants of type `isize`. However, the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout. ### Assigning discriminant values @@ -357,7 +357,6 @@ enum E { [numeric cast]: ../expressions/operator-expr.md#semantics [path expression]: ../expressions/path-expr.md [primitive representation]: ../type-layout.md#primitive-representations -[`Rust` representation]: ../type-layout.md#the-rust-representation [struct expression]: ../expressions/struct-expr.md [struct]: structs.md [type namespace]: ../names/namespaces.md diff --git a/src/type-layout.md b/src/type-layout.md index 3d65e3f19e..5326f30dfb 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -367,7 +367,14 @@ assert_eq!(std::mem::offset_of!(SizeRoundedUp, b), 0); r[layout.repr.c.enum] #### `#[repr(C)]` Field-less Enums -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`. +r[layout.repr.c.enum.discriminant] +For a [field-less enum] with the `C` representation, the discriminant values must either all be representable by the `int` type in the target platform's C ABI or all be representable by its `unsigned int` type. + +> [!NOTE] +> `repr(C)` enums without a primitive representation have discriminant values of type `isize`. See [items.enum.discriminant.type]. The size and alignment are determined from the discriminant values (according to the rule below) *after* they have been cast to `isize`. + +r[layout.repr.c.enum.size-align] +A [field-less enum] with the `C` representation has the same size and alignment as a C enum with the same discriminant values and no fixed underlying type. > [!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. @@ -643,6 +650,7 @@ Because this representation delegates type layout to another type, it cannot be [`Copy`]: std::marker::Copy [dynamically sized types]: dynamically-sized-types.md [enums]: items/enumerations.md +[field-less enum]: items.enum.fieldless [field-less enums]: items/enumerations.md#field-less-enum [field-struct-like variant]: EnumVariantStruct [fn-abi-compatibility]: ../core/primitive.fn.md#abi-compatibility From 99ff55e01b6494590ade5674e435ab8dfb08f8bb Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 22 Aug 2026 14:31:17 +0000 Subject: [PATCH 05/12] Note that `rustc` accepts bad discriminant values 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. --- src/type-layout.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/type-layout.md b/src/type-layout.md index 5326f30dfb..12b444f48c 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -373,6 +373,9 @@ For a [field-less enum] with the `C` representation, the discriminant values mus > [!NOTE] > `repr(C)` enums without a primitive representation have discriminant values of type `isize`. See [items.enum.discriminant.type]. The size and alignment are determined from the discriminant values (according to the rule below) *after* they have been cast to `isize`. +> [!NOTE] +> `rustc` accepts enums whose discriminant values do not meet this requirement but lints against them. This will become an error in the future. + r[layout.repr.c.enum.size-align] A [field-less enum] with the `C` representation has the same size and alignment as a C enum with the same discriminant values and no fixed underlying type. From 1724ca88e9831d4cfff5b3e82e3a200465a740e7 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sun, 23 Aug 2026 20:11:04 +0000 Subject: [PATCH 06/12] Drop inline link target in the portability note There's already a link reference definition for "primitive representation", so let's use that. --- src/type-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type-layout.md b/src/type-layout.md index 12b444f48c..f09a0f51a4 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -382,7 +382,7 @@ A [field-less enum] with the `C` representation has the same size and alignment > [!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) on the Rust side, and a fixed underlying type on the C side. +> For maximum portability, it is always preferred to set the size and alignment explicitly using a [primitive representation] on the Rust side, and a fixed underlying type on the C side. > [!WARNING] > There are crucial differences between an `enum` in the C language and Rust's [field-less enums] with this representation. An `enum` in C is mostly a `typedef` plus some named constants; in other words, an object of an `enum` type can hold any integer value. For example, this is often used for bitflags in `C`. In contrast, Rust’s [field-less enums] can only legally hold the discriminant values, everything else is [undefined behavior]. Therefore, using a field-less enum in FFI to model a C `enum` is often wrong. From f9c7e98a4b9d915a0047851a41c87450de07377a Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 22 Aug 2026 14:31:55 +0000 Subject: [PATCH 07/12] Reword `enum` portability note into the imperative mood 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. --- src/type-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type-layout.md b/src/type-layout.md index f09a0f51a4..fccc3dd7b1 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -382,7 +382,7 @@ A [field-less enum] with the `C` representation has the same size and alignment > [!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] on the Rust side, and a fixed underlying type on the C side. +> For maximum portability, prefer setting the size and alignment explicitly using a [primitive representation] on the Rust side and a fixed underlying type on the C side. > [!WARNING] > There are crucial differences between an `enum` in the C language and Rust's [field-less enums] with this representation. An `enum` in C is mostly a `typedef` plus some named constants; in other words, an object of an `enum` type can hold any integer value. For example, this is often used for bitflags in `C`. In contrast, Rust’s [field-less enums] can only legally hold the discriminant values, everything else is [undefined behavior]. Therefore, using a field-less enum in FFI to model a C `enum` is often wrong. From f5dc40c814902260982756b5faed3bd92ff627a9 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 22 Aug 2026 14:32:41 +0000 Subject: [PATCH 08/12] Add parenthetical about C23 and fixed underlying types We tell the reader to use a fixed underlying type in C. Let's mention the revision of C in which this was introduced. --- src/type-layout.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/type-layout.md b/src/type-layout.md index fccc3dd7b1..69e2576ac2 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -382,7 +382,7 @@ A [field-less enum] with the `C` representation has the same size and alignment > [!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, prefer setting the size and alignment explicitly using a [primitive representation] on the Rust side and a fixed underlying type on the C side. +> For maximum portability, prefer setting the size and alignment explicitly using a [primitive representation] on the Rust side and a fixed underlying type (introduced in C23) on the C side. > [!WARNING] > There are crucial differences between an `enum` in the C language and Rust's [field-less enums] with this representation. An `enum` in C is mostly a `typedef` plus some named constants; in other words, an object of an `enum` type can hold any integer value. For example, this is often used for bitflags in `C`. In contrast, Rust’s [field-less enums] can only legally hold the discriminant values, everything else is [undefined behavior]. Therefore, using a field-less enum in FFI to model a C `enum` is often wrong. From 88ca38a5e2498ebd014321be453cebd4686633b7 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Sat, 22 Aug 2026 14:33:44 +0000 Subject: [PATCH 09/12] Consolidate discriminant typing in the `enum` chapter 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. --- src/items/enumerations.md | 3 +++ src/type-layout.md | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/items/enumerations.md b/src/items/enumerations.md index 80a4244e28..0b41244e9b 100644 --- a/src/items/enumerations.md +++ b/src/items/enumerations.md @@ -117,6 +117,9 @@ Each enum instance has a _discriminant_: an integer logically associated to it t r[items.enum.discriminant.type] Enums without a [primitive representation] have discriminants of type `isize`. However, the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout. +r[items.enum.discriminant.type-primitive] +Enums with a [primitive representation] have discriminants of the type named by the representation. This also applies to enums that combine the `C` representation with a primitive representation (see [layout.repr.primitive-c]). For example, an enum with a `u8` representation can only have discriminant values between 0 and 255 inclusive. + ### Assigning discriminant values r[items.enum.discriminant.explicit] diff --git a/src/type-layout.md b/src/type-layout.md index 69e2576ac2..a5ff535346 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -463,7 +463,10 @@ Primitive representations can only be applied to enumerations and have different r[layout.repr.primitive.enum] #### Primitive representation of field-less enums -For [field-less enums], primitive representations set the type of the discriminants to the primitive type of the same name. Furthermore, the enum's size and alignment are guaranteed to match that type. For example, a field-less enum with a `u8` representation has discriminants of type `u8` and hence can only have discriminants between 0 and 255 inclusive. +A [field-less enum] with a primitive representation has the same size and alignment as the primitive type of the same name. + +> [!NOTE] +> Enums with a primitive representation have discriminant values of the type named by the representation. See [items.enum.discriminant.type-primitive]. r[layout.repr.primitive.adt] #### Primitive representation of enums with fields From 3eca88fdbe4041c953e42218bf08c02f651cb8b7 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 24 Aug 2026 03:23:51 +0000 Subject: [PATCH 10/12] Add examples for the discriminant type rules 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. --- src/items/enumerations.md | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/items/enumerations.md b/src/items/enumerations.md index 0b41244e9b..2e485ed15d 100644 --- a/src/items/enumerations.md +++ b/src/items/enumerations.md @@ -117,8 +117,38 @@ Each enum instance has a _discriminant_: an integer logically associated to it t r[items.enum.discriminant.type] Enums without a [primitive representation] have discriminants of type `isize`. However, the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout. +```rust +# use core::mem::size_of; +enum E { + V1 = 0isize, // OK: `isize` is the discriminant type. + V2, +} + +assert!(size_of::() <= size_of::()); +``` + +```rust,compile_fail,E0308 +enum E { + V = 0u8, // ERROR: Expected `isize`, found `u8`. +} +``` + r[items.enum.discriminant.type-primitive] -Enums with a [primitive representation] have discriminants of the type named by the representation. This also applies to enums that combine the `C` representation with a primitive representation (see [layout.repr.primitive-c]). For example, an enum with a `u8` representation can only have discriminant values between 0 and 255 inclusive. +Enums with a [primitive representation] have discriminants of the type named by the representation. This also applies to enums that combine the `C` representation with a primitive representation (see [layout.repr.primitive-c]). + +```rust +#[repr(u8)] +enum E { + V = 0u8, // OK: `u8` is the discriminant type. +} +``` + +```rust,compile_fail,E0308 +#[repr(u8)] +enum E { + V = 0isize, // ERROR: Expected `u8`, found `isize`. +} +``` ### Assigning discriminant values From 835307d5d1661d139a16a11344b78b71ff7203d5 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Mon, 24 Aug 2026 22:14:19 +0000 Subject: [PATCH 11/12] Add examples for the `enum` layout rules Let's add examples for the rules about the size and alignment of certain `enum`s. --- src/type-layout.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/src/type-layout.md b/src/type-layout.md index a5ff535346..70b26aac32 100644 --- a/src/type-layout.md +++ b/src/type-layout.md @@ -192,6 +192,35 @@ r[layout.repr.rust.enum-empty-zst] r[layout.repr.rust.enum-struct-like-zst] For [enums] (without a [primitive representation] specified) with a single [field-struct-like variant], a single [unit-struct-like variant], or a single [tuple-struct-like variant] and where the struct-like thing has no fields or where all of the fields are [zero sized], the enums themselves are [zero sized]. +```rust +# use core::mem::size_of; +enum E1 { + V {}, +} + +enum E2 { + V { f: () }, +} + +enum E3 { + V, +} + +enum E4 { + V(), +} + +enum E5 { + V(()), +} + +assert_eq!(size_of::(), 0); +assert_eq!(size_of::(), 0); +assert_eq!(size_of::(), 0); +assert_eq!(size_of::(), 0); +assert_eq!(size_of::(), 0); +``` + r[layout.repr.rust.unspecified] There are no other guarantees of data layout made by this representation. @@ -379,6 +408,22 @@ For a [field-less enum] with the `C` representation, the discriminant values mus r[layout.repr.c.enum.size-align] A [field-less enum] with the `C` representation has the same size and alignment as a C enum with the same discriminant values and no fixed underlying type. +```rust +# use core::ffi::c_int; +# use core::mem::{align_of, size_of}; +#[repr(C)] +enum E { + V1, + V2, +} + +#[cfg(target_arch = "x86_64")] +{ + assert_eq!(size_of::(), size_of::()); + assert_eq!(align_of::(), align_of::()); +} +``` + > [!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. > From 30b36b59fa605536b8bb5bd7abbbff553f0c5393 Mon Sep 17 00:00:00 2001 From: Travis Cross Date: Tue, 25 Aug 2026 07:08:00 +0000 Subject: [PATCH 12/12] Remove parens from nonparenthetical clause 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. --- src/items/enumerations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/items/enumerations.md b/src/items/enumerations.md index 2e485ed15d..3d7ecd6208 100644 --- a/src/items/enumerations.md +++ b/src/items/enumerations.md @@ -115,7 +115,7 @@ r[items.enum.discriminant.intro] Each enum instance has a _discriminant_: an integer logically associated to it that is used to determine which variant it holds. r[items.enum.discriminant.type] -Enums without a [primitive representation] have discriminants of type `isize`. However, the compiler may use a smaller type (or another means of distinguishing variants) in the actual memory layout. +Enums without a [primitive representation] have discriminants of type `isize`. However, the compiler may use a smaller type or another means of distinguishing variants in the actual memory layout. ```rust # use core::mem::size_of;