From e0cdce8267e80b10dfd1dd2a7e3d81c026296f14 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 1 Aug 2025 11:30:17 -0700 Subject: [PATCH 01/61] Create 0000-repr-ordered-fields.md initial RFC --- text/0000-repr-ordered-fields.md | 162 +++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 text/0000-repr-ordered-fields.md diff --git a/text/0000-repr-ordered-fields.md b/text/0000-repr-ordered-fields.md new file mode 100644 index 00000000000..98ed6303e88 --- /dev/null +++ b/text/0000-repr-ordered-fields.md @@ -0,0 +1,162 @@ +- Feature Name: (fill me in with a unique ident, `repr_ordered_fields`) +- Start Date: (fill me in with today's date, 2025-08-01) +- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) + +# Summary +[summary]: #summary + +Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC get's accepted) which can be applied to `struct`, `enum`, and `union` types which guarantees a simple, and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. + +# Motivation +[motivation]: #motivation + +Currently `repr(C)` serves two roles +1. Provide a consistent, cross-platform, predictable layout for a given type +2. Match the target C compiler's struct/union layout algorithm and ABI + +But in some cases, these two cases are in tension due to platform weirdness (even on major platforms like Windows MSVC) +* https://github.com/rust-lang/unsafe-code-guidelines/issues/521 +* https://github.com/rust-lang/rust/issues/81996 + +Providing any fix for case 2 would subtly break any users of case 1, which makes this difficult to fix within a single edition. +# Guide-level explanation +[guide-level-explanation]: #guide-level-explanation + +Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, and `union`. On all editions, `repr(ordered_fields)` would behave the same as `repr(C)` on edition 2024. (see reference level explanation for details). + +On editions 2024 (maybe <= 2024), any use of `repr(C)` will trigger a new warning, `edition_2024_repr_c` which will be warn by default. +This warning will suggest a machine applicable fix to switch `repr(C)` to `repr(ordered_fields)`, which is a no-op in the current edition, but helps prepare for changes to `repr(C)` early. This gives time for the community to switch over if they need to. + +For the FFI crates, they can safely ignore the warning by applying `#![allow(edition_2024_repr_c)]` to their crate root. +For crates without any FFI, they can simply run the machine applicable fix. +For crates with a mix, they will need to do some work to figure out which is which. But this is unavoidable to solve the stated motivation. + +For example, the warning could look like this: +``` +warning: use of `repr(C)` in type `Foo` + --> src/main.rs:14:10 + | +14 | #[repr(C)] + | ^^^^^^^ help: consider switching to `repr(ordered_fields)` + | struct Foo { + | + = note: `#[warn(edition_2024_repr_c)]` on by default + = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordred_fields)` +``` + + +On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. + +On all editions, once people have made the switch this will make it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop, if `repr(ordered_fields)` then it's for a dependable layout. Unlike today, where `repr(C)` fills both roles. + +# Reference-level explanation +[reference-level-explanation]: #reference-level-explanation + +This feature only touches `repr(C)`, other reprs are left as is. It introduces exactly one new repr, `repr(ordered_fields)` to take on one of the roles that `repr(C)` use to take. + +`repr(ordered_fields)` will use the same layout algorithm that `repr(C)` currently uses, details can be found in the [reference](https://doc.rust-lang.org/reference/type-layout.html?highlight=repr#reprc-structs). I will give an informal overview here. + +For structs, `repr(ordered_fields)` lays out each fields in memory according to the declaration order of the fields. + +```rust +#[repr(ordered_fields)] +struct Foo { + a: u32, + b: u8, + c: u32, + d: u16, +} +``` +Would be laid out like so (where `.` are padding bytes) +``` +#####...######.. +a b c d +``` + +For unions, each field is laid out at offset 0, and never have niches. + +For enums, the discriminant size is left unspecified (unless another repr specifies it like `repr(ordered_fields, u8)`), but is guaranteed to be stable across rust versions for a given set of variants and fields in each variant. + +Enums are defined as a union of structs, where each struct corresponds to each variant of the enum, with the discriminant is prepended as the first field. + +For example, `Foo` and `Bar` have the same layout in the example below modulo niches. + +```rust +#[repr(ordered_fields, u32)] +enum Foo { + Variant1, + Variant2(u8, u64), + Variant3 { + name: String, + } +} + +#[repr(ordered_fields)] +union Bar { + variant1: BarVariant1, + variant2: BarVariant2, + variant3: BarVariant3, +} + +#[repr(ordered_fields)] +struct BarVariant1 { + discr: u32, +} + +#[repr(ordered_fields)] +struct BarVariant2(u32, u8, u64); + +#[repr(ordered_fields)] +struct BarVariant3 { + discr: u32, + name: String, +} +``` + +Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, and `union`. On all editions, `repr(ordered_fields)` would behave the same as `repr(C)` on edition 2024. + +On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. For an extreme example, we could stop compiling `repr(C)` for ZST if the target C compiler doesn't allow ZSTs, or we could bump the size to 1 byte if the target C compiler does that by default (this is just an illustrative example, and not endorsed by RFC). + +As mentioned in the guide-level explanation, on edition 2024 (maybe <= 2024), any use of `repr(C)` would trigger a new warn by default diagnostic, `edition_2024_repr_c`. This warning could be phased out after at least two editions have passed. This gives the community enough time to migrate any code over to `repr(ordered_fields)` before the next edition after 2024, but doesn't burden Rust forever. + +The warning should come with a machine-applicable fix to switch `repr(C)` to `repr(ordered_fields)`, and this fix should be part of `cargo fix`. +# Drawbacks +[drawbacks]: #drawbacks + +* This will cause a large amount of churn in the Rust ecosystem +* If we don't end up actually switching `repr(C)` to mean the system layout/ABI, then we will have two identical reprs, which may cause confusion. +# Rationale and alternatives +[rationale-and-alternatives]: #rationale-and-alternatives + +* `crabi`: http://github.com/rust-lang/rfcs/pull/3470 + * Currently stuck in limbo since it has a much larger scope. doesn't actually serve to give a consistent cross-platform layout, since it defers to `repr(C)` (and it must, for it's stated goals) +* https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 + * This doesn't give a predictable layout that can be used to match the layouts (or prefixes) of different structs +* https://github.com/rust-lang/rfcs/pull/3718 + * This one is currently stuck due to a larger scope than this RFC +* do nothing + * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler. Or we break a bunch of subtle unsafe code to match the target C compiler. +# Prior art +[prior-art]: #prior-art + +This is discussed in Rationale and Alternatives +# Unresolved questions +[unresolved-questions]: #unresolved-questions + +* The migration plan, as a whole needs to be ironed out + * Currently it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. + * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. +* The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. + * `repr(linear)` + * `repr(ordered)` + * `repr(sequential)` + * something else? + +# Future possibilities +[future-possibilities]: #future-possibilities + +* Add more reprs for each target C compiler, for example `repr(C_gcc)` or `repr(C_msvc)`, etc. + * This would allow a single rust app to target multiple compilers in a robust way, and would make it easier to specify `repr(C)` + * This would also allow fixing code in older editions +* https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 From 0637bbfb1d27ac9c9a26d78b4e04093292eefc57 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 1 Aug 2025 11:32:58 -0700 Subject: [PATCH 02/61] Add a link to the zullip thread --- text/0000-repr-ordered-fields.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/0000-repr-ordered-fields.md b/text/0000-repr-ordered-fields.md index 98ed6303e88..2357a2403be 100644 --- a/text/0000-repr-ordered-fields.md +++ b/text/0000-repr-ordered-fields.md @@ -140,7 +140,10 @@ The warning should come with a machine-applicable fix to switch `repr(C)` to `re # Prior art [prior-art]: #prior-art -This is discussed in Rationale and Alternatives +See Rationale and Alternatives as well + +* https://rust-lang.zulipchat.com/#narrow/channel/213817-t-lang/topic/expand.2Frevise.20repr.28.7BC.2Clinear.2C.2E.2E.2E.7D.29.20for.202024.20edition + # Unresolved questions [unresolved-questions]: #unresolved-questions From 7e4b0c835c300fc993adabaafd4d62baa9ac6bf5 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 09:04:49 -0700 Subject: [PATCH 03/61] Update RFC with PR number --- ...0-repr-ordered-fields.md => 3845-repr-ordered-fields.md} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename text/{0000-repr-ordered-fields.md => 3845-repr-ordered-fields.md} (97%) diff --git a/text/0000-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md similarity index 97% rename from text/0000-repr-ordered-fields.md rename to text/3845-repr-ordered-fields.md index 2357a2403be..5c145224828 100644 --- a/text/0000-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -1,6 +1,6 @@ -- Feature Name: (fill me in with a unique ident, `repr_ordered_fields`) -- Start Date: (fill me in with today's date, 2025-08-01) -- RFC PR: [rust-lang/rfcs#0000](https://github.com/rust-lang/rfcs/pull/0000) +- Feature Name: `repr_ordered_fields` +- Start Date: 2025-08-05 +- RFC PR: [rust-lang/rfcs#3845](https://github.com/rust-lang/rfcs/pull/3845) - Rust Issue: [rust-lang/rust#0000](https://github.com/rust-lang/rust/issues/0000) # Summary From 61ce0f26031303e103ab4ddc4d9cb7325025793a Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 09:21:43 -0700 Subject: [PATCH 04/61] Grammer fixes --- text/3845-repr-ordered-fields.md | 38 ++++++++++++++++---------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 5c145224828..02f88161d98 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC get's accepted) which can be applied to `struct`, `enum`, and `union` types which guarantees a simple, and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. +Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC is accepted) that can be applied to `struct`, `enum`, and `union` types, which guarantees a simple and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. # Motivation [motivation]: #motivation @@ -25,11 +25,11 @@ Providing any fix for case 2 would subtly break any users of case 1, which makes Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, and `union`. On all editions, `repr(ordered_fields)` would behave the same as `repr(C)` on edition 2024. (see reference level explanation for details). -On editions 2024 (maybe <= 2024), any use of `repr(C)` will trigger a new warning, `edition_2024_repr_c` which will be warn by default. -This warning will suggest a machine applicable fix to switch `repr(C)` to `repr(ordered_fields)`, which is a no-op in the current edition, but helps prepare for changes to `repr(C)` early. This gives time for the community to switch over if they need to. +In editions 2024 (maybe <= 2024), any use of `repr(C)` will trigger a new warning, `edition_2024_repr_c` which will be warn by default. +This warning suggests a machine-applicable fix to switch `repr(C)` to `repr(ordered_fields)`, which is a no-op in the current edition, but helps prepare for changes to `repr(C)` early. This gives time for the community to update their code as needed. For the FFI crates, they can safely ignore the warning by applying `#![allow(edition_2024_repr_c)]` to their crate root. -For crates without any FFI, they can simply run the machine applicable fix. +For crates without any FFI, they can simply run the machine-applicable fix. For crates with a mix, they will need to do some work to figure out which is which. But this is unavoidable to solve the stated motivation. For example, the warning could look like this: @@ -42,22 +42,22 @@ warning: use of `repr(C)` in type `Foo` | struct Foo { | = note: `#[warn(edition_2024_repr_c)]` on by default - = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordred_fields)` + = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordered_fields)` ``` On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. -On all editions, once people have made the switch this will make it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop, if `repr(ordered_fields)` then it's for a dependable layout. Unlike today, where `repr(C)` fills both roles. +On all editions, once people have made the switch, this will make it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. This is more clear than today, where `repr(C)` fills both roles. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This feature only touches `repr(C)`, other reprs are left as is. It introduces exactly one new repr, `repr(ordered_fields)` to take on one of the roles that `repr(C)` use to take. +This feature only touches `repr(C)`, other reprs are left as is. It introduces exactly one new repr, `repr(ordered_fields)`, to take on one of the roles that `repr(C)` used to take. -`repr(ordered_fields)` will use the same layout algorithm that `repr(C)` currently uses, details can be found in the [reference](https://doc.rust-lang.org/reference/type-layout.html?highlight=repr#reprc-structs). I will give an informal overview here. +`repr(ordered_fields)` will use the same layout algorithm that `repr(C)` currently uses. Details can be found in the [reference](https://doc.rust-lang.org/reference/type-layout.html?highlight=repr#reprc-structs). I will give an informal overview here. -For structs, `repr(ordered_fields)` lays out each fields in memory according to the declaration order of the fields. +For structs, `repr(ordered_fields)` lays out each field in memory according to the declaration order of the fields. ```rust #[repr(ordered_fields)] @@ -74,11 +74,11 @@ Would be laid out like so (where `.` are padding bytes) a b c d ``` -For unions, each field is laid out at offset 0, and never have niches. +For unions, each field is laid out at offset 0, and never has niches. -For enums, the discriminant size is left unspecified (unless another repr specifies it like `repr(ordered_fields, u8)`), but is guaranteed to be stable across rust versions for a given set of variants and fields in each variant. +For enums, the discriminant size is left unspecified (unless another repr specifies it like `repr(ordered_fields, u8)`), but is guaranteed to be stable across Rust versions for a given set of variants and fields in each variant. -Enums are defined as a union of structs, where each struct corresponds to each variant of the enum, with the discriminant is prepended as the first field. +Enums are defined as a union of structs, where each struct corresponds to each variant of the enum, with the discriminant prepended as the first field. For example, `Foo` and `Bar` have the same layout in the example below modulo niches. @@ -118,25 +118,25 @@ Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. For an extreme example, we could stop compiling `repr(C)` for ZST if the target C compiler doesn't allow ZSTs, or we could bump the size to 1 byte if the target C compiler does that by default (this is just an illustrative example, and not endorsed by RFC). -As mentioned in the guide-level explanation, on edition 2024 (maybe <= 2024), any use of `repr(C)` would trigger a new warn by default diagnostic, `edition_2024_repr_c`. This warning could be phased out after at least two editions have passed. This gives the community enough time to migrate any code over to `repr(ordered_fields)` before the next edition after 2024, but doesn't burden Rust forever. +As mentioned in the guide-level explanation, in edition 2024 (maybe <= 2024), any use of `repr(C)` would trigger a new warn by default diagnostic, `edition_2024_repr_c`. This warning could be phased out after at least two editions have passed. This gives the community enough time to migrate any code over to `repr(ordered_fields)` before the next edition after 2024, but doesn't burden Rust forever. The warning should come with a machine-applicable fix to switch `repr(C)` to `repr(ordered_fields)`, and this fix should be part of `cargo fix`. # Drawbacks [drawbacks]: #drawbacks * This will cause a large amount of churn in the Rust ecosystem -* If we don't end up actually switching `repr(C)` to mean the system layout/ABI, then we will have two identical reprs, which may cause confusion. +* If we don't end up switching `repr(C)` to mean the system layout/ABI, then we will have two identical reprs, which may cause confusion. # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives * `crabi`: http://github.com/rust-lang/rfcs/pull/3470 - * Currently stuck in limbo since it has a much larger scope. doesn't actually serve to give a consistent cross-platform layout, since it defers to `repr(C)` (and it must, for it's stated goals) + * Currently stuck in limbo since it has a much larger scope. doesn't actually serve to give a consistent cross-platform layout, since it defers to `repr(C)` (and it must, for its stated goals) * https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 * This doesn't give a predictable layout that can be used to match the layouts (or prefixes) of different structs * https://github.com/rust-lang/rfcs/pull/3718 * This one is currently stuck due to a larger scope than this RFC * do nothing - * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler. Or we break a bunch of subtle unsafe code to match the target C compiler. + * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler, or we break a bunch of subtle unsafe code to match the target C compiler. # Prior art [prior-art]: #prior-art @@ -147,8 +147,8 @@ See Rationale and Alternatives as well # Unresolved questions [unresolved-questions]: #unresolved-questions -* The migration plan, as a whole needs to be ironed out - * Currently it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. +* The migration plan, as a whole, needs to be ironed out + * Currently, it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. * The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. * `repr(linear)` @@ -160,6 +160,6 @@ See Rationale and Alternatives as well [future-possibilities]: #future-possibilities * Add more reprs for each target C compiler, for example `repr(C_gcc)` or `repr(C_msvc)`, etc. - * This would allow a single rust app to target multiple compilers in a robust way, and would make it easier to specify `repr(C)` + * This would allow a single Rust app to target multiple compilers robustly, and would make it easier to specify `repr(C)` * This would also allow fixing code in older editions * https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 From 4fa572e57773c9baf3bd15d854e83b047a73570d Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 12:27:04 -0700 Subject: [PATCH 05/61] Update Motivation to include the exact issue from MSVC --- text/3845-repr-ordered-fields.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 02f88161d98..dbf0e46f91d 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -20,6 +20,17 @@ But in some cases, these two cases are in tension due to platform weirdness (eve * https://github.com/rust-lang/rust/issues/81996 Providing any fix for case 2 would subtly break any users of case 1, which makes this difficult to fix within a single edition. + +As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) + +```rust +// should have size 8, but has size 0 +#[repr(C)] +struct SomeFFI([i64; 0]); +``` + +Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 755644ca339c04c7efada4b002283ac6955ae5bf Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 15:45:13 -0700 Subject: [PATCH 06/61] Rework reference level section add more unresolved questions --- text/3845-repr-ordered-fields.md | 239 ++++++++++++++++++++++++------- 1 file changed, 189 insertions(+), 50 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index dbf0e46f91d..fd6dbc549c3 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -34,16 +34,12 @@ Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for c # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, and `union`. On all editions, `repr(ordered_fields)` would behave the same as `repr(C)` on edition 2024. (see reference level explanation for details). +`repr(ordered_fields)` is a new representation that can be applied to `struct`, `enum`, and `union` to give them a consistent, cross-platform, and predictable in memory layout. -In editions 2024 (maybe <= 2024), any use of `repr(C)` will trigger a new warning, `edition_2024_repr_c` which will be warn by default. -This warning suggests a machine-applicable fix to switch `repr(C)` to `repr(ordered_fields)`, which is a no-op in the current edition, but helps prepare for changes to `repr(C)` early. This gives time for the community to update their code as needed. +`repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. -For the FFI crates, they can safely ignore the warning by applying `#![allow(edition_2024_repr_c)]` to their crate root. -For crates without any FFI, they can simply run the machine-applicable fix. -For crates with a mix, they will need to do some work to figure out which is which. But this is unavoidable to solve the stated motivation. +Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as a future compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. -For example, the warning could look like this: ``` warning: use of `repr(C)` in type `Foo` --> src/main.rs:14:10 @@ -56,82 +52,217 @@ warning: use of `repr(C)` in type `Foo` = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordered_fields)` ``` - -On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. - -On all editions, once people have made the switch, this will make it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. This is more clear than today, where `repr(C)` fills both roles. - +After enough time has passed, and the community has switched over: +This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -This feature only touches `repr(C)`, other reprs are left as is. It introduces exactly one new repr, `repr(ordered_fields)`, to take on one of the roles that `repr(C)` used to take. +## `repr(C)` -`repr(ordered_fields)` will use the same layout algorithm that `repr(C)` currently uses. Details can be found in the [reference](https://doc.rust-lang.org/reference/type-layout.html?highlight=repr#reprc-structs). I will give an informal overview here. +> The `C` representation is designed for one purpose: creating types that are interoperable with the C Language. +> +> This representation can be applied to structs, unions, and enums. The exception is [zero-variant enums](https://doc.rust-lang.org/stable/reference/items/enumerations.html#zero-variant-enums) for which the `C` representation is an error. +> +> - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` -For structs, `repr(ordered_fields)` lays out each field in memory according to the declaration order of the fields. +The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). +## `repr(ordered_fields)` + +> The `ordered_fields` representation is designed for one purpose: create types that you can soundly perform operations on that rely on data layout such as reinterpreting values as a different type +> +> This representation can be applied to structs, unions, and enums. +> +> - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` +### struct +Structs are laid out in memory in declaration order. ```rust +// size 16, align 4 #[repr(ordered_fields)] -struct Foo { - a: u32, - b: u8, - c: u32, - d: u16, +struct FooStruct { + a: u8, + b: u32, + c: u16, + d: u32, } ``` -Would be laid out like so (where `.` are padding bytes) + +Would be laid out in memory like so + +``` +a...bbbbcc..dddd ``` -#####...######.. -a b c d +### union +```rust +// size 4, align 4 +#[repr(ordered_fields)] +union FooUnion { + a: u8, + b: u32, + c: u16, + d: u32, +} ``` -For unions, each field is laid out at offset 0, and never has niches. +Would have the same layout as `u32`. +### enum +The enum discriminant will be the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. -For enums, the discriminant size is left unspecified (unless another repr specifies it like `repr(ordered_fields, u8)`), but is guaranteed to be stable across Rust versions for a given set of variants and fields in each variant. +If an enum doesn't have any fields, then it is represented exactly by it's discriminant. +```rust +// discriminant = i16 +// represented as i16 +#[repr(ordered_fields)] +enum FooEnum { + VarA = 1, + VarB, // discriminant = 2 + VarC = 500, + VarD, // discriminant = 501 +} -Enums are defined as a union of structs, where each struct corresponds to each variant of the enum, with the discriminant prepended as the first field. +// discriminant = u16 +// represented as u16 +#[repr(ordered_fields, u16)] +enum FooEnumUnsigned { + VarA = 1, + VarB, // discriminant = 2 + VarC = 500, + VarD, // discriminant = 501 +} +``` -For example, `Foo` and `Bar` have the same layout in the example below modulo niches. +Enums with fields will be laid out as if they were a union of structs. +For example, this would be laid out the same as the union below ```rust -#[repr(ordered_fields, u32)] -enum Foo { - Variant1, - Variant2(u8, u64), - Variant3 { - name: String, - } +#[repr(ordered_fields)] +enum BarEnum { + VarFieldless, + VarTuple(u8, u32), + VarStruct { + a: u16, + b: u32, + }, } +``` +```rust #[repr(ordered_fields)] -union Bar { - variant1: BarVariant1, - variant2: BarVariant2, - variant3: BarVariant3, +union BarUnion { + var1: VarFieldless, + var2: VarTuple, + var3: VarStruct, } #[repr(ordered_fields)] -struct BarVariant1 { - discr: u32, +enum BarDiscriminant { + VarFieldless, + VarTuple, + VarStruct, } #[repr(ordered_fields)] -struct BarVariant2(u32, u8, u64); +struct VarFieldless { + disc: BarDiscriminant, +} #[repr(ordered_fields)] -struct BarVariant3 { - discr: u32, - name: String, -} +struct VarTuple(BarDiscriminant, u8, u32); + +#[repr(ordered_fields)] +struct VarStruct(BarDiscriminant, u16, u32); ``` -Introduce a new `repr(ordered_fields)` which can be applied to `struct`, `enum`, and `union`. On all editions, `repr(ordered_fields)` would behave the same as `repr(C)` on edition 2024. +In Rust, the algorithm for calculating the layout is defined precisely as follows -On editions > 2024, `repr(ordered_fields)` may differ from `repr(C)`, so that `repr(C)` can match the platform's layout algorithm. For an extreme example, we could stop compiling `repr(C)` for ZST if the target C compiler doesn't allow ZSTs, or we could bump the size to 1 byte if the target C compiler does that by default (this is just an illustrative example, and not endorsed by RFC). +```rust +/// Takes in the layout of each field (in declaration order) +/// and returns the offsets of each field, and layout of the entire struct +fn get_layout_for_struct(field_layouts: &[Layout]) -> Result<(Vec, Layout), LayoutError> { + let mut layout = Layout::new::<()>(); + let mut field_offsets = Vec::new(); + + for &field in field_layouts { + let (next_layout, offset) = layout.extend(field)?; + + field_offsets.push(offset); + layout = next_layout; + } + + Ok((field_offsets, layout.pad_to_align())) +} -As mentioned in the guide-level explanation, in edition 2024 (maybe <= 2024), any use of `repr(C)` would trigger a new warn by default diagnostic, `edition_2024_repr_c`. This warning could be phased out after at least two editions have passed. This gives the community enough time to migrate any code over to `repr(ordered_fields)` before the next edition after 2024, but doesn't burden Rust forever. +fn layout_max(a: Layout, b: Layout) -> Result { + Layout::from_size_align( + a.size().max(b.size()), + a.align().max(b.align()), + ) +} + +/// Takes in the layout of each field (in declaration order) +/// and returns the layout of the entire union +/// NOTE: all fields of the union are located at offset 0 +fn get_layout_for_union(field_layouts: &[Layout]) -> Result { + let mut layout = Layout::new::<()>(); + + for &field in field_layouts { + layout = layout_max(layout, field)?; + } + + Ok(layout.pad_to_align()) +} + +/// Takes in the layout of each variant (and their fields) (in declaration order) +/// and returns the offsets of all fields of the enum, and the layout of the entire enum +/// NOTE: all fields of the enum discriminant is always at offset 0 +fn get_layout_for_enum( + // the discriminants may be negative for some enums + // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger + discriminants: &[BigInteger], + variant_layouts: &[&[Layout]] +) -> Result<(Vec>, Layout), LayoutError> { + assert_eq!(discriminants.len(), variant_layouts.len()); + + let mut layout = Layout::new::<()>(); + let mut variant_field_offsets = Vec::new(); + + let mut variant_with_disc = Vec::new(); + // gives the smallest integer type which can represent the variants and the specified discriminants + let disc_layout = get_layout_for_discriminant(discriminants); + // ensure that the discriminant is the first field + variant_with_disc.push(disc_layout); + + for &variant in variant_layouts { + variant_with_disc.truncate(1); + // put all other fields of the variant after this one + variant_with_disc.extend_from_slice(variant); + let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_disc)?; + // remove the discriminant so the caller only gets the fields they provided in order + offsets.remove(0); + + variant_field_offsets.push(offsets); + layout = layout_max(layout, variant_layout)?; + } + + Ok((variant_field_offsets, layout)) +} +``` +### Migration to `repr(ordered_fields)` + +The migration will be handled as follows: +* after `repr(ordered_fields)` is implemented + * add a future compatibility warning for `repr(C)` in all current editions + * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior + * the warning will come with a machine-applicable fix + * Any crate which does no FFI can just apply the autofix + * Any crate which uses `repr(C)` for FFI can ignore the warning crate-wide + * Any crate which mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crate) +* Once the next edition rolls around (2027?), `repr(C)` on the new edition will *not* warn. Instead the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. + * The warning for previous editions will continue to be in effect +* In some future edition (2033+), when it is deemed safe enough, the future compatibility warnings may be removed in editions <= 2024 + * This should have given plenty of time for anyone who was going to update their code to do so. And doesn't burden the language indefinitely + * This part isn't strictly necessary, and can be removed if needed -The warning should come with a machine-applicable fix to switch `repr(C)` to `repr(ordered_fields)`, and this fix should be part of `cargo fix`. # Drawbacks [drawbacks]: #drawbacks @@ -165,8 +296,16 @@ See Rationale and Alternatives as well * `repr(linear)` * `repr(ordered)` * `repr(sequential)` + * `repr(consistent)` * something else? - +* Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? +* Should unions expose some niches? + * For example, if all variants of the union are structs which have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making union of structs behave more like an enum). + * This must be answered before stabilization, as it is set in stone after that +* Should this `repr` be versioned? + * This way we can evolve the repr (for example, by adding new niches) +* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. + * Leaning towards no # Future possibilities [future-possibilities]: #future-possibilities From 0d0a79b9a38e6841f672d3512253fcd214fa598e Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 15:49:42 -0700 Subject: [PATCH 07/61] Add description for union layout Rework struct layout description. --- text/3845-repr-ordered-fields.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index fd6dbc549c3..84e8d7ee0cf 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -74,7 +74,8 @@ The exact algorithm is deferred to whatever the default target C compiler does w > > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` ### struct -Structs are laid out in memory in declaration order. +Structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. +And their alignment would be the same as their most aligned field. ```rust // size 16, align 4 @@ -93,6 +94,8 @@ Would be laid out in memory like so a...bbbbcc..dddd ``` ### union +Unions would be laid out with the same size as their largest field, and the same alignment as their most aligned field. + ```rust // size 4, align 4 #[repr(ordered_fields)] From 4f4bf18d5b7a8ef337950b2d6ff80200bc19e781 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 15:50:43 -0700 Subject: [PATCH 08/61] Tabs -> Spaces --- text/3845-repr-ordered-fields.md | 202 +++++++++++++++---------------- 1 file changed, 101 insertions(+), 101 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 84e8d7ee0cf..c009ecbee24 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -81,10 +81,10 @@ And their alignment would be the same as their most aligned field. // size 16, align 4 #[repr(ordered_fields)] struct FooStruct { - a: u8, - b: u32, - c: u16, - d: u32, + a: u8, + b: u32, + c: u16, + d: u32, } ``` @@ -100,10 +100,10 @@ Unions would be laid out with the same size as their largest field, and the same // size 4, align 4 #[repr(ordered_fields)] union FooUnion { - a: u8, - b: u32, - c: u16, - d: u32, + a: u8, + b: u32, + c: u16, + d: u32, } ``` @@ -117,20 +117,20 @@ If an enum doesn't have any fields, then it is represented exactly by it's discr // represented as i16 #[repr(ordered_fields)] enum FooEnum { - VarA = 1, - VarB, // discriminant = 2 - VarC = 500, - VarD, // discriminant = 501 + VarA = 1, + VarB, // discriminant = 2 + VarC = 500, + VarD, // discriminant = 501 } // discriminant = u16 // represented as u16 #[repr(ordered_fields, u16)] enum FooEnumUnsigned { - VarA = 1, - VarB, // discriminant = 2 - VarC = 500, - VarD, // discriminant = 501 + VarA = 1, + VarB, // discriminant = 2 + VarC = 500, + VarD, // discriminant = 501 } ``` @@ -140,33 +140,33 @@ For example, this would be laid out the same as the union below ```rust #[repr(ordered_fields)] enum BarEnum { - VarFieldless, - VarTuple(u8, u32), - VarStruct { - a: u16, - b: u32, - }, + VarFieldless, + VarTuple(u8, u32), + VarStruct { + a: u16, + b: u32, + }, } ``` ```rust #[repr(ordered_fields)] union BarUnion { - var1: VarFieldless, - var2: VarTuple, - var3: VarStruct, + var1: VarFieldless, + var2: VarTuple, + var3: VarStruct, } #[repr(ordered_fields)] enum BarDiscriminant { - VarFieldless, - VarTuple, - VarStruct, + VarFieldless, + VarTuple, + VarStruct, } #[repr(ordered_fields)] struct VarFieldless { - disc: BarDiscriminant, + disc: BarDiscriminant, } #[repr(ordered_fields)] @@ -182,89 +182,89 @@ In Rust, the algorithm for calculating the layout is defined precisely as follow /// Takes in the layout of each field (in declaration order) /// and returns the offsets of each field, and layout of the entire struct fn get_layout_for_struct(field_layouts: &[Layout]) -> Result<(Vec, Layout), LayoutError> { - let mut layout = Layout::new::<()>(); - let mut field_offsets = Vec::new(); - - for &field in field_layouts { - let (next_layout, offset) = layout.extend(field)?; - - field_offsets.push(offset); - layout = next_layout; - } - - Ok((field_offsets, layout.pad_to_align())) + let mut layout = Layout::new::<()>(); + let mut field_offsets = Vec::new(); + + for &field in field_layouts { + let (next_layout, offset) = layout.extend(field)?; + + field_offsets.push(offset); + layout = next_layout; + } + + Ok((field_offsets, layout.pad_to_align())) } fn layout_max(a: Layout, b: Layout) -> Result { - Layout::from_size_align( - a.size().max(b.size()), - a.align().max(b.align()), - ) + Layout::from_size_align( + a.size().max(b.size()), + a.align().max(b.align()), + ) } /// Takes in the layout of each field (in declaration order) /// and returns the layout of the entire union /// NOTE: all fields of the union are located at offset 0 fn get_layout_for_union(field_layouts: &[Layout]) -> Result { - let mut layout = Layout::new::<()>(); - - for &field in field_layouts { - layout = layout_max(layout, field)?; - } - - Ok(layout.pad_to_align()) + let mut layout = Layout::new::<()>(); + + for &field in field_layouts { + layout = layout_max(layout, field)?; + } + + Ok(layout.pad_to_align()) } /// Takes in the layout of each variant (and their fields) (in declaration order) /// and returns the offsets of all fields of the enum, and the layout of the entire enum /// NOTE: all fields of the enum discriminant is always at offset 0 fn get_layout_for_enum( - // the discriminants may be negative for some enums - // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger - discriminants: &[BigInteger], - variant_layouts: &[&[Layout]] + // the discriminants may be negative for some enums + // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger + discriminants: &[BigInteger], + variant_layouts: &[&[Layout]] ) -> Result<(Vec>, Layout), LayoutError> { - assert_eq!(discriminants.len(), variant_layouts.len()); - + assert_eq!(discriminants.len(), variant_layouts.len()); + let mut layout = Layout::new::<()>(); - let mut variant_field_offsets = Vec::new(); - - let mut variant_with_disc = Vec::new(); - // gives the smallest integer type which can represent the variants and the specified discriminants + let mut variant_field_offsets = Vec::new(); + + let mut variant_with_disc = Vec::new(); + // gives the smallest integer type which can represent the variants and the specified discriminants let disc_layout = get_layout_for_discriminant(discriminants); - // ensure that the discriminant is the first field + // ensure that the discriminant is the first field variant_with_disc.push(disc_layout); - for &variant in variant_layouts { - variant_with_disc.truncate(1); - // put all other fields of the variant after this one - variant_with_disc.extend_from_slice(variant); - let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_disc)?; - // remove the discriminant so the caller only gets the fields they provided in order - offsets.remove(0); - - variant_field_offsets.push(offsets); - layout = layout_max(layout, variant_layout)?; - } - - Ok((variant_field_offsets, layout)) + for &variant in variant_layouts { + variant_with_disc.truncate(1); + // put all other fields of the variant after this one + variant_with_disc.extend_from_slice(variant); + let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_disc)?; + // remove the discriminant so the caller only gets the fields they provided in order + offsets.remove(0); + + variant_field_offsets.push(offsets); + layout = layout_max(layout, variant_layout)?; + } + + Ok((variant_field_offsets, layout)) } ``` ### Migration to `repr(ordered_fields)` The migration will be handled as follows: * after `repr(ordered_fields)` is implemented - * add a future compatibility warning for `repr(C)` in all current editions - * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior - * the warning will come with a machine-applicable fix - * Any crate which does no FFI can just apply the autofix - * Any crate which uses `repr(C)` for FFI can ignore the warning crate-wide - * Any crate which mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crate) + * add a future compatibility warning for `repr(C)` in all current editions + * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior + * the warning will come with a machine-applicable fix + * Any crate which does no FFI can just apply the autofix + * Any crate which uses `repr(C)` for FFI can ignore the warning crate-wide + * Any crate which mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crate) * Once the next edition rolls around (2027?), `repr(C)` on the new edition will *not* warn. Instead the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. - * The warning for previous editions will continue to be in effect + * The warning for previous editions will continue to be in effect * In some future edition (2033+), when it is deemed safe enough, the future compatibility warnings may be removed in editions <= 2024 - * This should have given plenty of time for anyone who was going to update their code to do so. And doesn't burden the language indefinitely - * This part isn't strictly necessary, and can be removed if needed + * This should have given plenty of time for anyone who was going to update their code to do so. And doesn't burden the language indefinitely + * This part isn't strictly necessary, and can be removed if needed # Drawbacks [drawbacks]: #drawbacks @@ -275,13 +275,13 @@ The migration will be handled as follows: [rationale-and-alternatives]: #rationale-and-alternatives * `crabi`: http://github.com/rust-lang/rfcs/pull/3470 - * Currently stuck in limbo since it has a much larger scope. doesn't actually serve to give a consistent cross-platform layout, since it defers to `repr(C)` (and it must, for its stated goals) + * Currently stuck in limbo since it has a much larger scope. doesn't actually serve to give a consistent cross-platform layout, since it defers to `repr(C)` (and it must, for its stated goals) * https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 - * This doesn't give a predictable layout that can be used to match the layouts (or prefixes) of different structs + * This doesn't give a predictable layout that can be used to match the layouts (or prefixes) of different structs * https://github.com/rust-lang/rfcs/pull/3718 - * This one is currently stuck due to a larger scope than this RFC + * This one is currently stuck due to a larger scope than this RFC * do nothing - * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler, or we break a bunch of subtle unsafe code to match the target C compiler. + * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler, or we break a bunch of subtle unsafe code to match the target C compiler. # Prior art [prior-art]: #prior-art @@ -293,26 +293,26 @@ See Rationale and Alternatives as well [unresolved-questions]: #unresolved-questions * The migration plan, as a whole, needs to be ironed out - * Currently, it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. - * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. + * Currently, it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. + * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. * The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. - * `repr(linear)` - * `repr(ordered)` - * `repr(sequential)` - * `repr(consistent)` - * something else? + * `repr(linear)` + * `repr(ordered)` + * `repr(sequential)` + * `repr(consistent)` + * something else? * Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? * Should unions expose some niches? - * For example, if all variants of the union are structs which have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making union of structs behave more like an enum). - * This must be answered before stabilization, as it is set in stone after that + * For example, if all variants of the union are structs which have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making union of structs behave more like an enum). + * This must be answered before stabilization, as it is set in stone after that * Should this `repr` be versioned? - * This way we can evolve the repr (for example, by adding new niches) + * This way we can evolve the repr (for example, by adding new niches) * Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. - * Leaning towards no + * Leaning towards no # Future possibilities [future-possibilities]: #future-possibilities * Add more reprs for each target C compiler, for example `repr(C_gcc)` or `repr(C_msvc)`, etc. - * This would allow a single Rust app to target multiple compilers robustly, and would make it easier to specify `repr(C)` - * This would also allow fixing code in older editions + * This would allow a single Rust app to target multiple compilers robustly, and would make it easier to specify `repr(C)` + * This would also allow fixing code in older editions * https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247 From 063af084d8cf1eb52eaac879d0a09ffa0766a68d Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 16:18:40 -0700 Subject: [PATCH 09/61] Apply suggestions from code review Co-authored-by: Jacob Lifshay --- text/3845-repr-ordered-fields.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c009ecbee24..7920c6c6e0b 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -75,7 +75,7 @@ The exact algorithm is deferred to whatever the default target C compiler does w > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` ### struct Structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. -And their alignment would be the same as their most aligned field. +And their alignment is the same as their most aligned field. ```rust // size 16, align 4 @@ -107,9 +107,9 @@ union FooUnion { } ``` -Would have the same layout as `u32`. +`FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum -The enum discriminant will be the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. +The enum discriminant is the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. If an enum doesn't have any fields, then it is represented exactly by it's discriminant. ```rust @@ -176,11 +176,11 @@ struct VarTuple(BarDiscriminant, u8, u32); struct VarStruct(BarDiscriminant, u16, u32); ``` -In Rust, the algorithm for calculating the layout is defined precisely as follows +In Rust, the algorithm for calculating the layout is defined precisely as follows: ```rust /// Takes in the layout of each field (in declaration order) -/// and returns the offsets of each field, and layout of the entire struct +/// and returns the offsets of each field, and the layout of the entire struct fn get_layout_for_struct(field_layouts: &[Layout]) -> Result<(Vec, Layout), LayoutError> { let mut layout = Layout::new::<()>(); let mut field_offsets = Vec::new(); @@ -217,7 +217,7 @@ fn get_layout_for_union(field_layouts: &[Layout]) -> Result /// Takes in the layout of each variant (and their fields) (in declaration order) /// and returns the offsets of all fields of the enum, and the layout of the entire enum -/// NOTE: all fields of the enum discriminant is always at offset 0 +/// NOTE: the enum discriminant is always at offset 0 fn get_layout_for_enum( // the discriminants may be negative for some enums // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger From 0c0e429cd3c2fc244804a4bc4d8e0d2c5c178f4a Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 16:31:12 -0700 Subject: [PATCH 10/61] discriminant -> tag --- text/3845-repr-ordered-fields.md | 40 ++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 7920c6c6e0b..dac652cfd5f 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -34,7 +34,7 @@ Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for c # Guide-level explanation [guide-level-explanation]: #guide-level-explanation -`repr(ordered_fields)` is a new representation that can be applied to `struct`, `enum`, and `union` to give them a consistent, cross-platform, and predictable in memory layout. +`repr(ordered_fields)` is a new representation that can be applied to `struct`, `enum`, and `union` to give them a consistent, cross-platform, and predictable in-memory layout. `repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. @@ -109,11 +109,11 @@ union FooUnion { `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum -The enum discriminant is the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. +The enum's tag is the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. If an enum doesn't have any fields, then it is represented exactly by it's discriminant. ```rust -// discriminant = i16 +// tag = i16 // represented as i16 #[repr(ordered_fields)] enum FooEnum { @@ -123,7 +123,7 @@ enum FooEnum { VarD, // discriminant = 501 } -// discriminant = u16 +// tag = u16 // represented as u16 #[repr(ordered_fields, u16)] enum FooEnumUnsigned { @@ -158,7 +158,7 @@ union BarUnion { } #[repr(ordered_fields)] -enum BarDiscriminant { +enum BarTag { VarFieldless, VarTuple, VarStruct, @@ -166,14 +166,18 @@ enum BarDiscriminant { #[repr(ordered_fields)] struct VarFieldless { - disc: BarDiscriminant, + tag: BarTag, } #[repr(ordered_fields)] -struct VarTuple(BarDiscriminant, u8, u32); +struct VarTuple(BarTag, u8, u32); #[repr(ordered_fields)] -struct VarStruct(BarDiscriminant, u16, u32); +struct VarStruct { + tag: BarTag, + a: u16, + b: u32 +} ``` In Rust, the algorithm for calculating the layout is defined precisely as follows: @@ -217,7 +221,7 @@ fn get_layout_for_union(field_layouts: &[Layout]) -> Result /// Takes in the layout of each variant (and their fields) (in declaration order) /// and returns the offsets of all fields of the enum, and the layout of the entire enum -/// NOTE: the enum discriminant is always at offset 0 +/// NOTE: the enum tag is always at offset 0 fn get_layout_for_enum( // the discriminants may be negative for some enums // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger @@ -229,18 +233,18 @@ fn get_layout_for_enum( let mut layout = Layout::new::<()>(); let mut variant_field_offsets = Vec::new(); - let mut variant_with_disc = Vec::new(); + let mut variant_with_tag = Vec::new(); // gives the smallest integer type which can represent the variants and the specified discriminants - let disc_layout = get_layout_for_discriminant(discriminants); - // ensure that the discriminant is the first field - variant_with_disc.push(disc_layout); + let tag_layout = get_layout_for_tag(discriminants); + // ensure that the tag is the first field + variant_with_tag.push(tag_layout); for &variant in variant_layouts { - variant_with_disc.truncate(1); + variant_with_tag.truncate(1); // put all other fields of the variant after this one - variant_with_disc.extend_from_slice(variant); - let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_disc)?; - // remove the discriminant so the caller only gets the fields they provided in order + variant_with_tag.extend_from_slice(variant); + let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_tag)?; + // remove the tag so the caller only gets the fields they provided in order offsets.remove(0); variant_field_offsets.push(offsets); @@ -309,6 +313,8 @@ See Rationale and Alternatives as well * This way we can evolve the repr (for example, by adding new niches) * Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. * Leaning towards no +* Should we warn on `repr(ordered_fields)` when explicit tag type is specified (i.e. no `repr(u8)`/`repr(i32)`) + * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type. # Future possibilities [future-possibilities]: #future-possibilities From 9e316ff9367589fece8099ce3e97926da4a519d5 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 16:33:24 -0700 Subject: [PATCH 11/61] Qualify alignment assumptions --- text/3845-repr-ordered-fields.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index dac652cfd5f..c9d7c3b6764 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -78,6 +78,7 @@ Structs are laid out in memory in declaration order, with padding bytes added as And their alignment is the same as their most aligned field. ```rust +// assuming that u32 is aligned to 4 bytes // size 16, align 4 #[repr(ordered_fields)] struct FooStruct { @@ -97,6 +98,7 @@ a...bbbbcc..dddd Unions would be laid out with the same size as their largest field, and the same alignment as their most aligned field. ```rust +// assuming that u32 is aligned to 4 bytes // size 4, align 4 #[repr(ordered_fields)] union FooUnion { From a1700b66bb549365fb414378ec40507ea71ff9b1 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 5 Aug 2025 18:58:03 -0700 Subject: [PATCH 12/61] oops, fix typo --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c9d7c3b6764..b1a680341c6 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -315,7 +315,7 @@ See Rationale and Alternatives as well * This way we can evolve the repr (for example, by adding new niches) * Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. * Leaning towards no -* Should we warn on `repr(ordered_fields)` when explicit tag type is specified (i.e. no `repr(u8)`/`repr(i32)`) +* Should we warn on `repr(ordered_fields)` when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type. # Future possibilities [future-possibilities]: #future-possibilities From d75a497c82a6d520d3c2328cb42bda977d559b60 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 6 Aug 2025 15:16:17 -0700 Subject: [PATCH 13/61] add `repr(declaration_order)` as a potential spelling --- text/3845-repr-ordered-fields.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index b1a680341c6..0750369eab1 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -306,6 +306,7 @@ See Rationale and Alternatives as well * `repr(ordered)` * `repr(sequential)` * `repr(consistent)` + * `repr(declaration_order)` * something else? * Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? * Should unions expose some niches? From 6526f5ac7d81bb66fc197ce1e3bddc5285e81c8d Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 6 Aug 2025 15:28:14 -0700 Subject: [PATCH 14/61] Switch enum tag type to defer to `repr(C)` tag type --- text/3845-repr-ordered-fields.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 0750369eab1..b00ba100d1b 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -111,13 +111,14 @@ union FooUnion { `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum -The enum's tag is the smallest signed integer type which can hold all of the discriminant values (unless otherwise specified). The discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. +The enum's tag type is same type that is used for `repr(C)` in edition <= 2024, and the discriminants is assigned the same was as `repr(C)` (in edition <= 2024). This means the discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. +This does mean that the tag type will be platform specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning. This warning should suggest the smallest integer type which can hold the discriminant values (preferring signed integers to break ties). If an enum doesn't have any fields, then it is represented exactly by it's discriminant. ```rust // tag = i16 // represented as i16 -#[repr(ordered_fields)] +#[repr(ordered_fields, i16)] enum FooEnum { VarA = 1, VarB, // discriminant = 2 @@ -140,7 +141,7 @@ Enums with fields will be laid out as if they were a union of structs. For example, this would be laid out the same as the union below ```rust -#[repr(ordered_fields)] +#[repr(ordered_fields, i8)] enum BarEnum { VarFieldless, VarTuple(u8, u32), @@ -159,7 +160,7 @@ union BarUnion { var3: VarStruct, } -#[repr(ordered_fields)] +#[repr(ordered_fields, i8)] enum BarTag { VarFieldless, VarTuple, From ed96c1b4cb44650f971abd5127bcfd277c02721c Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 7 Aug 2025 08:16:12 -0700 Subject: [PATCH 15/61] Rework edition_2024_repr_c warning from future compat to edition warning --- text/3845-repr-ordered-fields.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index b00ba100d1b..c004d0e2ca0 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -38,7 +38,7 @@ Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for c `repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. -Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as a future compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. +Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an optional edition migration lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. ``` warning: use of `repr(C)` in type `Foo` @@ -112,7 +112,7 @@ union FooUnion { `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum The enum's tag type is same type that is used for `repr(C)` in edition <= 2024, and the discriminants is assigned the same was as `repr(C)` (in edition <= 2024). This means the discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. -This does mean that the tag type will be platform specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning. This warning should suggest the smallest integer type which can hold the discriminant values (preferring signed integers to break ties). +This does mean that the tag type will be platform specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). This warning should suggest the smallest integer type which can hold the discriminant values (preferring signed integers to break ties). If an enum doesn't have any fields, then it is represented exactly by it's discriminant. ```rust @@ -261,23 +261,23 @@ fn get_layout_for_enum( The migration will be handled as follows: * after `repr(ordered_fields)` is implemented - * add a future compatibility warning for `repr(C)` in all current editions + * add an optional edition migration warning for `repr(C)` + * this warning should be advertised publicly (maybe on the Rust Blog?), so that as many people use it. Since even if you are staying on edition <= 2024, it is helpful to switch to `repr(ordered_fields)` to make your intentions clearer * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior * the warning will come with a machine-applicable fix - * Any crate which does no FFI can just apply the autofix + * Any crate that does not have FFI can just apply the autofix * Any crate which uses `repr(C)` for FFI can ignore the warning crate-wide - * Any crate which mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crate) -* Once the next edition rolls around (2027?), `repr(C)` on the new edition will *not* warn. Instead the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. + * Any crate that mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crates) +* Once the next edition rolls around (2027?), `repr(C)` on the new edition will *not* warn. Instead, the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. * The warning for previous editions will continue to be in effect -* In some future edition (2033+), when it is deemed safe enough, the future compatibility warnings may be removed in editions <= 2024 - * This should have given plenty of time for anyone who was going to update their code to do so. And doesn't burden the language indefinitely - * This part isn't strictly necessary, and can be removed if needed # Drawbacks [drawbacks]: #drawbacks * This will cause a large amount of churn in the Rust ecosystem + * This is only necessary for those who are updating to the new edition. Which is as little churn as we can make it * If we don't end up switching `repr(C)` to mean the system layout/ABI, then we will have two identical reprs, which may cause confusion. + # Rationale and alternatives [rationale-and-alternatives]: #rationale-and-alternatives From 01cfb408ef8f05b61831c1e51ae176e47f02e5a9 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 7 Aug 2025 08:19:46 -0700 Subject: [PATCH 16/61] Add lints to summary section --- text/3845-repr-ordered-fields.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c004d0e2ca0..3db21c9dfe6 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -6,7 +6,11 @@ # Summary [summary]: #summary -Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC is accepted) that can be applied to `struct`, `enum`, and `union` types, which guarantees a simple and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. +Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC is accepted) that can be applied to `struct`, `enum`, and `union` types, which guarantees a simple and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. This allows restricting the meaning of `repr(C)` to just serve the FFI use-case. + +Introduce two new warnings +1. An optional edition warning, when updating to the next edition, that the meaning of `repr(C)` is changing. +2. A warning-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted. # Motivation [motivation]: #motivation From f11567c3bfb74d1ea8d71ca4bf4f43e82c9e2407 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 7 Aug 2025 08:20:59 -0700 Subject: [PATCH 17/61] edition migraiton lint -> edition compatibility lint --- text/3845-repr-ordered-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 3db21c9dfe6..a7bed2723d1 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -42,7 +42,7 @@ Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for c `repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. -Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an optional edition migration lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. +Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an optional edition compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. ``` warning: use of `repr(C)` in type `Foo` @@ -265,7 +265,7 @@ fn get_layout_for_enum( The migration will be handled as follows: * after `repr(ordered_fields)` is implemented - * add an optional edition migration warning for `repr(C)` + * add an optional edition compatibility lint for `repr(C)` * this warning should be advertised publicly (maybe on the Rust Blog?), so that as many people use it. Since even if you are staying on edition <= 2024, it is helpful to switch to `repr(ordered_fields)` to make your intentions clearer * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior * the warning will come with a machine-applicable fix From b17f19288be573d961518187c4bcfeecea163f59 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 7 Aug 2025 10:28:15 -0700 Subject: [PATCH 18/61] fix code example of layout algorithm for enums --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index a7bed2723d1..c8f62a1b6cf 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -241,7 +241,7 @@ fn get_layout_for_enum( let mut variant_field_offsets = Vec::new(); let mut variant_with_tag = Vec::new(); - // gives the smallest integer type which can represent the variants and the specified discriminants + // gives the tag used by `repr(C)` enums let tag_layout = get_layout_for_tag(discriminants); // ensure that the tag is the first field variant_with_tag.push(tag_layout); From 488068ef147bce4f5c21d44c626e8666d05ec783 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 11 Aug 2025 08:26:38 -0700 Subject: [PATCH 19/61] Add reference to MSVC bug in motivation --- text/3845-repr-ordered-fields.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c8f62a1b6cf..4712f0172cc 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -10,7 +10,7 @@ Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bi Introduce two new warnings 1. An optional edition warning, when updating to the next edition, that the meaning of `repr(C)` is changing. -2. A warning-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted. +2. A warn-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted. # Motivation [motivation]: #motivation @@ -35,6 +35,9 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). +A tertiary motivation is to make progress on a work around for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). This proposal doesn't attempt a complete solution for the bug, but it will be a necessary component of any solution to the bug. + +The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the `alignof` macro reports an alignment 4 bytes, but in structs it is aligned to 8 bytes. And on these platforms, we report the alignment as 8 bytes. Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 93f53a5e54278d59d39fc996ae89286247419d62 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 11 Aug 2025 08:34:43 -0700 Subject: [PATCH 20/61] Add the suspicious_repr_c lint --- text/3845-repr-ordered-fields.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 4712f0172cc..44a66badc1f 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -11,6 +11,7 @@ Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bi Introduce two new warnings 1. An optional edition warning, when updating to the next edition, that the meaning of `repr(C)` is changing. 2. A warn-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted. +3. A warn-by-default lint when `repr(C)` is used, and there are no `extern` blocks or functions in the crate (on all editions). # Motivation [motivation]: #motivation @@ -59,6 +60,22 @@ warning: use of `repr(C)` in type `Foo` = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordered_fields)` ``` +Using `repr(C)` on all editions (including > 2024) when there are no extern blocks or functions in the crate will trigger a warn-by-default lint suggesting to use `repr(ordered_fields)`. Since the most likely reason to do this is if you haven't heard of `repr(ordered_fields)` or are upgrading to the most recent Rust version (which now contains `repr(ordered_fields)`). + +If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. + +``` +warning: use of `repr(C)` in type `Foo` + --> src/main.rs:14:10 + | +14 | #[repr(C)] + | ^^^^^^^ help: consider switching to `repr(ordered_fields)` + | struct Foo { + | + = note: `#[warn(suspicious_repr_c)]` on by default + = note: `repr(C)` is intended for FFI, and since there are no `extern` blocks or functions, it's likely that you meant to use `repr(ordered_fields)` to get a stable and consistent layout for your type +``` + After enough time has passed, and the community has switched over: This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. # Reference-level explanation @@ -326,6 +343,7 @@ See Rationale and Alternatives as well * Leaning towards no * Should we warn on `repr(ordered_fields)` when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type. +* What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) # Future possibilities [future-possibilities]: #future-possibilities From a2737d52bad5469ebb02bfea6d7d506d187e2ea5 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 11 Aug 2025 08:36:42 -0700 Subject: [PATCH 21/61] specify precedence of `suspicious_repr_c` lint --- text/3845-repr-ordered-fields.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 44a66badc1f..d62b28418db 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -64,6 +64,8 @@ Using `repr(C)` on all editions (including > 2024) when there are no extern bloc If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. +The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. + ``` warning: use of `repr(C)` in type `Foo` --> src/main.rs:14:10 From bb8a392bf960d4486e3ba10de7bc2c5fa62df885 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 11 Aug 2025 08:41:32 -0700 Subject: [PATCH 22/61] minor grammer/punctuation fixes --- text/3845-repr-ordered-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index d62b28418db..a51e85b545e 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -36,9 +36,9 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). -A tertiary motivation is to make progress on a work around for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). This proposal doesn't attempt a complete solution for the bug, but it will be a necessary component of any solution to the bug. +A tertiary motivation is to make progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). This proposal doesn't attempt a complete solution for the bug, but it will be a necessary component of any solution to the bug. -The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the `alignof` macro reports an alignment 4 bytes, but in structs it is aligned to 8 bytes. And on these platforms, we report the alignment as 8 bytes. Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. +The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the `alignof` macro reports an alignment of 4 bytes, but in structs, it is aligned to 8 bytes. And on these platforms, we report the alignment as 8 bytes. Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 612b99e4dbee0b2b10a986f56a2467b02dc7d7eb Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 11 Aug 2025 09:33:08 -0700 Subject: [PATCH 23/61] Fix MSVC bug description --- text/3845-repr-ordered-fields.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index a51e85b545e..c98ee2caf30 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -38,7 +38,9 @@ Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for c A tertiary motivation is to make progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). This proposal doesn't attempt a complete solution for the bug, but it will be a necessary component of any solution to the bug. -The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the `alignof` macro reports an alignment of 4 bytes, but in structs, it is aligned to 8 bytes. And on these platforms, we report the alignment as 8 bytes. Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. +The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8-bytes, and may instead only align them to 4 bytes. + +Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 283df469ec9128e40f311a2cecc9eab2453afffb Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 18 Aug 2025 10:19:58 -0700 Subject: [PATCH 24/61] Update text/3845-repr-ordered-fields.md Co-authored-by: +merlan #flirora --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c98ee2caf30..02ae90d917b 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -103,7 +103,7 @@ The exact algorithm is deferred to whatever the default target C compiler does w > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` ### struct Structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. -And their alignment is the same as their most aligned field. +The alignment of a struct is the same as the alignment of the most aligned field. ```rust // assuming that u32 is aligned to 4 bytes From 8fe15764915700ac2fc46b72c7ccde9bf9f6e034 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 18 Aug 2025 10:54:12 -0700 Subject: [PATCH 25/61] Update from reviews * update repr(ordered_fields) algorithm for `enum` * add to and update motivation section * add potential drawbacks to some lints --- text/3845-repr-ordered-fields.md | 73 +++++++++++++++++--------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 02ae90d917b..feaca39cc6e 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -9,9 +9,9 @@ Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC is accepted) that can be applied to `struct`, `enum`, and `union` types, which guarantees a simple and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. This allows restricting the meaning of `repr(C)` to just serve the FFI use-case. Introduce two new warnings -1. An optional edition warning, when updating to the next edition, that the meaning of `repr(C)` is changing. -2. A warn-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted. -3. A warn-by-default lint when `repr(C)` is used, and there are no `extern` blocks or functions in the crate (on all editions). +1. An edition migration warning, when updating to the next edition, that the meaning of `repr(C)` is changing +2. A warn-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted +3. A warn-by-default lint when `repr(C)` is used, and there are no `extern` blocks or functions in the crate (on all editions) # Motivation [motivation]: #motivation @@ -36,7 +36,13 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). -A tertiary motivation is to make progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). This proposal doesn't attempt a complete solution for the bug, but it will be a necessary component of any solution to the bug. +The next two cases will not be solved by this RFC, but this RFC will provide the necessary parts steps towards the respective fixes. + +This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. + +By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases. + +The final motivation is to make progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8-bytes, and may instead only align them to 4 bytes. @@ -66,6 +72,8 @@ Using `repr(C)` on all editions (including > 2024) when there are no extern bloc If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. +This does miss one potential use-case, where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. This should be an extremely small minority of crates, and they can silence this warning crate-wide. + The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. ``` @@ -165,7 +173,7 @@ enum FooEnumUnsigned { } ``` -Enums with fields will be laid out as if they were a union of structs. +Enums with fields will be laid out as if they were a struct containing the tag and a union of structs containing the data. For example, this would be laid out the same as the union below ```rust @@ -182,7 +190,13 @@ enum BarEnum { ```rust #[repr(ordered_fields)] -union BarUnion { +struct BarEnumRepr { + tag: BarTag, + data: BarEnumData, +} + +#[repr(ordered_fields)] +union BarEnumData { var1: VarFieldless, var2: VarTuple, var3: VarStruct, @@ -196,16 +210,13 @@ enum BarTag { } #[repr(ordered_fields)] -struct VarFieldless { - tag: BarTag, -} +struct VarFieldless; #[repr(ordered_fields)] -struct VarTuple(BarTag, u8, u32); +struct VarTuple(u8, u32); #[repr(ordered_fields)] struct VarStruct { - tag: BarTag, a: u16, b: u32 } @@ -250,39 +261,31 @@ fn get_layout_for_union(field_layouts: &[Layout]) -> Result Ok(layout.pad_to_align()) } -/// Takes in the layout of each variant (and their fields) (in declaration order) -/// and returns the offsets of all fields of the enum, and the layout of the entire enum +/// Takes in the layout of each variant (and their fields) (in declaration order), and returns the layout of the entire enum +/// the offsets of all fields of the enum is left as an excersize for the readers /// NOTE: the enum tag is always at offset 0 fn get_layout_for_enum( // the discriminants may be negative for some enums // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger discriminants: &[BigInteger], variant_layouts: &[&[Layout]] -) -> Result<(Vec>, Layout), LayoutError> { +) -> Result { assert_eq!(discriminants.len(), variant_layouts.len()); + + let variant_data_layout = variant_layouts.iter() + .try_fold( + Layout::new::<()>(), + |acc, variant_layout| Ok(layout_max(acc, get_layout_for_struct(variant_layout)?.1)?) + )?; - let mut layout = Layout::new::<()>(); - let mut variant_field_offsets = Vec::new(); - - let mut variant_with_tag = Vec::new(); - // gives the tag used by `repr(C)` enums let tag_layout = get_layout_for_tag(discriminants); - // ensure that the tag is the first field - variant_with_tag.push(tag_layout); - - for &variant in variant_layouts { - variant_with_tag.truncate(1); - // put all other fields of the variant after this one - variant_with_tag.extend_from_slice(variant); - let (mut offsets, variant_layout) = get_layout_for_struct(&variant_with_tag)?; - // remove the tag so the caller only gets the fields they provided in order - offsets.remove(0); - - variant_field_offsets.push(offsets); - layout = layout_max(layout, variant_layout)?; - } - - Ok((variant_field_offsets, layout)) + + let (_, layout) = get_layout_for_struct(&[ + tag_layout, + variant_data_layout + ])?; + + Ok(layout) } ``` ### Migration to `repr(ordered_fields)` From 489b31a7de57c984cc8be80f34995760ad919f6c Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 18 Aug 2025 11:25:19 -0700 Subject: [PATCH 26/61] fix typo --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index feaca39cc6e..b5e47154cb0 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -36,7 +36,7 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). -The next two cases will not be solved by this RFC, but this RFC will provide the necessary parts steps towards the respective fixes. +The next two cases will not be solved by this RFC, but this RFC will provide the necessary steps towards the respective fixes. This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. From a5fb9bc5204dda45016583d2f672e928ff8cb2cb Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 18 Aug 2025 16:04:45 -0700 Subject: [PATCH 27/61] Fix typo Co-authored-by: Jacob Lifshay --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index b5e47154cb0..7c5a0777c45 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -262,7 +262,7 @@ fn get_layout_for_union(field_layouts: &[Layout]) -> Result } /// Takes in the layout of each variant (and their fields) (in declaration order), and returns the layout of the entire enum -/// the offsets of all fields of the enum is left as an excersize for the readers +/// the offsets of all fields of the enum is left as an exercise for the readers /// NOTE: the enum tag is always at offset 0 fn get_layout_for_enum( // the discriminants may be negative for some enums From 88f631e1288b77b8d9091fdd8cfb0f0ea754948f Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 20 Aug 2025 13:58:25 -0700 Subject: [PATCH 28/61] Add AIX to motivation --- text/3845-repr-ordered-fields.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 7c5a0777c45..3cdc8f7523d 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -42,11 +42,20 @@ This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases. -The final motivation is to make progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). +Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480) and a similar AIX [issue](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594). The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8-bytes, and may instead only align them to 4 bytes. Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. + +For AIX, the issue is that `f64` is treated as aligned to 4-bytes if it is not the first field in a struct. i.e. +```C +struct Foo { + char a; + double b; +} +``` +Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has alignment 8 in Rust). Again, any proper workaround will require reducing the alignment of `f64`, and adjusting `repr(C)`. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From dee831da9e3f4b53ef9e0247b755f0186d0afe06 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 21 Aug 2025 09:12:01 -0700 Subject: [PATCH 29/61] Add some description for why it's a problem to conflate the two roles of repr(C) --- text/3845-repr-ordered-fields.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 3cdc8f7523d..7b5fcad58be 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -24,7 +24,16 @@ But in some cases, these two cases are in tension due to platform weirdness (eve * https://github.com/rust-lang/unsafe-code-guidelines/issues/521 * https://github.com/rust-lang/rust/issues/81996 -Providing any fix for case 2 would subtly break any users of case 1, which makes this difficult to fix within a single edition. +Code in case 1 generally falls into one of these buckets: +* rely on the exact layout being consistent across platforms + * for example, zero-copy deserialization (see [rkyv](https://crates.io/crates/rkyv)) +* be manually calculating the offsets of fields + * This is common in code written before the stabilization of `offset_of` (currently only stabilized for `struct`) +* be manually calculating the layout for a DST, to prepare an allocation (see [slice-dst](https://crates.io/crates/slice-dst), specifically [here](https://github.com/CAD97/pointer-utils/blob/0fe399f8f7e519959224069360f3900189086683/crates/slice-dst/src/lib.rs#L162-L163)) +* to match layouts of two different types (or even, two different monomorphizations of the same generic type) + * see [here](https://github.com/rust-lang/rust/pull/68099), where in `alloc` this is done for `Rc` and `Arc` to give a consistent layout for all `T` + +So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it relies on unsafe code making assumptions about data layouts, which makes this difficult to fix within a single edition. As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) @@ -56,6 +65,8 @@ struct Foo { } ``` Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has alignment 8 in Rust). Again, any proper workaround will require reducing the alignment of `f64`, and adjusting `repr(C)`. + + # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From f007f6f5532c259a970fc9420c54f67f88b8a9c4 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 21 Aug 2025 12:27:16 -0700 Subject: [PATCH 30/61] wording improvements for the motivation --- text/3845-repr-ordered-fields.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 7b5fcad58be..c1c5874b7ec 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -27,10 +27,11 @@ But in some cases, these two cases are in tension due to platform weirdness (eve Code in case 1 generally falls into one of these buckets: * rely on the exact layout being consistent across platforms * for example, zero-copy deserialization (see [rkyv](https://crates.io/crates/rkyv)) -* be manually calculating the offsets of fields - * This is common in code written before the stabilization of `offset_of` (currently only stabilized for `struct`) -* be manually calculating the layout for a DST, to prepare an allocation (see [slice-dst](https://crates.io/crates/slice-dst), specifically [here](https://github.com/CAD97/pointer-utils/blob/0fe399f8f7e519959224069360f3900189086683/crates/slice-dst/src/lib.rs#L162-L163)) -* to match layouts of two different types (or even, two different monomorphizations of the same generic type) +* manually calculating the offsets of fields + * This is common in code written before the stabilization of `offset_of` (currently only stabilized for `struct`)/`&raw const`/`&raw mut` + * But sometimes this is still required if you are doing manually type-erasure and handling DSTs (for example, implementing [`erasable::Erasable`](https://docs.rs/erasable/1.3.0/erasable/trait.Erasable.html)) +* manually calculating the layout for a DST, to prepare an allocation (see [slice-dst](https://crates.io/crates/slice-dst), specifically [here](https://github.com/CAD97/pointer-utils/blob/0fe399f8f7e519959224069360f3900189086683/crates/slice-dst/src/lib.rs#L162-L163)) +* match layouts of two different types (or even, two different monomorphizations of the same generic type) * see [here](https://github.com/rust-lang/rust/pull/68099), where in `alloc` this is done for `Rc` and `Arc` to give a consistent layout for all `T` So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it relies on unsafe code making assumptions about data layouts, which makes this difficult to fix within a single edition. From 703dcb9879d0240d825adbcbb53a1af241e3c985 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 21 Aug 2025 12:28:36 -0700 Subject: [PATCH 31/61] fix typo --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c1c5874b7ec..1b44059d32c 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -29,7 +29,7 @@ Code in case 1 generally falls into one of these buckets: * for example, zero-copy deserialization (see [rkyv](https://crates.io/crates/rkyv)) * manually calculating the offsets of fields * This is common in code written before the stabilization of `offset_of` (currently only stabilized for `struct`)/`&raw const`/`&raw mut` - * But sometimes this is still required if you are doing manually type-erasure and handling DSTs (for example, implementing [`erasable::Erasable`](https://docs.rs/erasable/1.3.0/erasable/trait.Erasable.html)) + * But sometimes this is still required if you are manually doing type-erasure and handling DSTs (for example, implementing [`erasable::Erasable`](https://docs.rs/erasable/1.3.0/erasable/trait.Erasable.html)) * manually calculating the layout for a DST, to prepare an allocation (see [slice-dst](https://crates.io/crates/slice-dst), specifically [here](https://github.com/CAD97/pointer-utils/blob/0fe399f8f7e519959224069360f3900189086683/crates/slice-dst/src/lib.rs#L162-L163)) * match layouts of two different types (or even, two different monomorphizations of the same generic type) * see [here](https://github.com/rust-lang/rust/pull/68099), where in `alloc` this is done for `Rc` and `Arc` to give a consistent layout for all `T` From 2e364547e87e9304ddf87944eadeb02f29994a92 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 08:10:45 -0700 Subject: [PATCH 32/61] Apply suggestions from code review Co-authored-by: Ralf Jung --- text/3845-repr-ordered-fields.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 1b44059d32c..18147848d1c 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -36,7 +36,7 @@ Code in case 1 generally falls into one of these buckets: So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it relies on unsafe code making assumptions about data layouts, which makes this difficult to fix within a single edition. -As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) +As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) ```rust // should have size 8, but has size 0 @@ -46,6 +46,11 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). +Fixing the layout of this struct will also most likely let us remove a long-standing hack from our implementation of the MSVC ABI: +unlike all other ABIs, we do *not* entirely skip ZST with that ABI but instead mark them to be passed by-ptr. +This is needed to correctly pass types like `SomeFFI`. +If we fix our layout computation to match that of MSVC, we no longer need this special case in the ABI logic. + The next two cases will not be solved by this RFC, but this RFC will provide the necessary steps towards the respective fixes. This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. From 472cae7c9032e4833c06cf1ebc8eaaa5321ae40e Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 08:15:29 -0700 Subject: [PATCH 33/61] Minor rewordings --- text/3845-repr-ordered-fields.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 18147848d1c..dd6760c114b 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -34,7 +34,7 @@ Code in case 1 generally falls into one of these buckets: * match layouts of two different types (or even, two different monomorphizations of the same generic type) * see [here](https://github.com/rust-lang/rust/pull/68099), where in `alloc` this is done for `Rc` and `Arc` to give a consistent layout for all `T` -So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it relies on unsafe code making assumptions about data layouts, which makes this difficult to fix within a single edition. +So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it affects unsafe code making assumptions about data layouts. Making it difficult to fix within a single edition/existing editions. As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) @@ -80,7 +80,7 @@ Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has `repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. -Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an optional edition compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. +Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an edition migration compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. ``` warning: use of `repr(C)` in type `Foo` @@ -318,7 +318,7 @@ fn get_layout_for_enum( The migration will be handled as follows: * after `repr(ordered_fields)` is implemented - * add an optional edition compatibility lint for `repr(C)` + * add an edition migration lint for `repr(C)` * this warning should be advertised publicly (maybe on the Rust Blog?), so that as many people use it. Since even if you are staying on edition <= 2024, it is helpful to switch to `repr(ordered_fields)` to make your intentions clearer * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior * the warning will come with a machine-applicable fix @@ -374,8 +374,8 @@ See Rationale and Alternatives as well * This way we can evolve the repr (for example, by adding new niches) * Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. * Leaning towards no -* Should we warn on `repr(ordered_fields)` when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) - * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type. +* Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) + * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) # Future possibilities [future-possibilities]: #future-possibilities From 6c48b1757b9be2167b771afee01c8a17d8ff5345 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 08:48:27 -0700 Subject: [PATCH 34/61] Add sections to motivation and expand AIX to it's own section --- text/3845-repr-ordered-fields.md | 40 ++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index dd6760c114b..69a74075c9f 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -36,6 +36,15 @@ Code in case 1 generally falls into one of these buckets: So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it affects unsafe code making assumptions about data layouts. Making it difficult to fix within a single edition/existing editions. +Here are some examples of the tension and some other RFCs which could benefit from splitting up `repr(C)`'s two cases. + +1. Windows MSVC ZSTs +2. the RFC [#3718](https://github.com/rust-lang/rfcs/pull/3718) for `repr(C, packed(N))` containing overaligned fields +3. A Windows MSVC bug +4. An [AIX](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594) layout bug + +## MSVC ZST + As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) ```rust @@ -53,11 +62,15 @@ If we fix our layout computation to match that of MSVC, we no longer need this s The next two cases will not be solved by this RFC, but this RFC will provide the necessary steps towards the respective fixes. +## RFC #3718 + This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. -By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases. +By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases[1](ordered_fields_align). + +## MSVC bug -Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480) and a similar AIX [issue](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594). +Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8-bytes, and may instead only align them to 4 bytes. @@ -72,7 +85,27 @@ struct Foo { ``` Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has alignment 8 in Rust). Again, any proper workaround will require reducing the alignment of `f64`, and adjusting `repr(C)`. +## AIX layout bug + +For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). + +In AIX, the following struct `Floats` has the following field offsets: `[0, 64, 96]` (in bits) + +```C +struct Floats { + double a; + char b; + double c; +}; +``` + +This is because +> In aggregates, the first member of this data type is aligned according to its natural alignment value; subsequent members of the aggregate are aligned on 4-byte boundaries. +> - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1) + +Even though on AIX `alignof(double)` is 8, it is still laid out an a 4-byte boundary. +This is in stark contrast with `repr(C)` in Rust, which always lays out fields at their "natural alignment". Any fix for this would require splitting up `repr(C)` since anyone in case 2 cannot tolerate under-aligned fields (since it would disallow taking references to those fields). # Guide-level explanation [guide-level-explanation]: #guide-level-explanation @@ -377,6 +410,9 @@ See Rationale and Alternatives as well * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). + * discussion: https://github.com/rust-lang/rfcs/pull/3845/files#r2319098177 + * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) # Future possibilities [future-possibilities]: #future-possibilities From 2043a027a67320ed27471ad5ea121c356ee4e0cb Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 08:54:15 -0700 Subject: [PATCH 35/61] try fix intradoc link? --- text/3845-repr-ordered-fields.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 69a74075c9f..9a8f8520378 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -66,7 +66,7 @@ The next two cases will not be solved by this RFC, but this RFC will provide the This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. -By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases[1](ordered_fields_align). +By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases[1](#ordered_fields_align). ## MSVC bug @@ -410,7 +410,7 @@ See Rationale and Alternatives as well * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) -* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845/files#r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) # Future possibilities From 4c81c7ce13fe4c3d4a83aba7cbddc8b5e823b1b6 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 09:02:24 -0700 Subject: [PATCH 36/61] Minor rewording of motivation section --- text/3845-repr-ordered-fields.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 9a8f8520378..a4ebd2deb2d 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -43,6 +43,8 @@ Here are some examples of the tension and some other RFCs which could benefit fr 3. A Windows MSVC bug 4. An [AIX](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594) layout bug +Examples 3 and 4 cannot be solved with this RFC alone, but any fix would require splitting up `repr(C)`. + ## MSVC ZST As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) @@ -60,8 +62,6 @@ unlike all other ABIs, we do *not* entirely skip ZST with that ABI but instead m This is needed to correctly pass types like `SomeFFI`. If we fix our layout computation to match that of MSVC, we no longer need this special case in the ABI logic. -The next two cases will not be solved by this RFC, but this RFC will provide the necessary steps towards the respective fixes. - ## RFC #3718 This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. @@ -410,7 +410,7 @@ See Rationale and Alternatives as well * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) -* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845/files#r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) # Future possibilities From 92eb763f05c301be24a1063ebcb285c8acedc82c Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 09:08:08 -0700 Subject: [PATCH 37/61] Add unresolved question for `repr(C)` where corrosponding C type doesn't compile --- text/3845-repr-ordered-fields.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index a4ebd2deb2d..b1ae35e0686 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -410,9 +410,11 @@ See Rationale and Alternatives as well * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) -* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845/files#r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) +* What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? + * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 # Future possibilities [future-possibilities]: #future-possibilities From 9fb58ad9cf667b2ab534f6a8e8dbbaeae8e89375 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 09:09:28 -0700 Subject: [PATCH 38/61] fix link --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index b1ae35e0686..971e81456e2 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -411,7 +411,7 @@ See Rationale and Alternatives as well * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) * Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). - * discussion: https://github.com/rust-lang/rfcs/pull/3845/files#r2319098177 + * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) * What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 From 6a161954cf069df7c70855bc32aeab6d4194cac9 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 10:01:36 -0700 Subject: [PATCH 39/61] `align` -> `__alignof__` on AIX --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 971e81456e2..5a66c07d103 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -103,7 +103,7 @@ This is because > In aggregates, the first member of this data type is aligned according to its natural alignment value; subsequent members of the aggregate are aligned on 4-byte boundaries. > - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1) -Even though on AIX `alignof(double)` is 8, it is still laid out an a 4-byte boundary. +Even though on AIX `__alignof__(double)` is 8, it is still laid out an a 4-byte boundary. This is in stark contrast with `repr(C)` in Rust, which always lays out fields at their "natural alignment". Any fix for this would require splitting up `repr(C)` since anyone in case 2 cannot tolerate under-aligned fields (since it would disallow taking references to those fields). # Guide-level explanation From e7bd72a29ecd5bfb396616a5016b34f870c076b6 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 14:15:36 -0700 Subject: [PATCH 40/61] Apply suggestions from code review Co-authored-by: Ralf Jung --- text/3845-repr-ordered-fields.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 5a66c07d103..0cbba9251c1 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -89,7 +89,7 @@ Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). -In AIX, the following struct `Floats` has the following field offsets: `[0, 64, 96]` (in bits) +In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) ```C struct Floats { @@ -103,7 +103,8 @@ This is because > In aggregates, the first member of this data type is aligned according to its natural alignment value; subsequent members of the aggregate are aligned on 4-byte boundaries. > - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1) -Even though on AIX `__alignof__(double)` is 8, it is still laid out an a 4-byte boundary. +Even though on AIX `__alignof__(double)` is 8, it is still laid out an a 4-byte boundary. +(That's no contradiction since `__alignof__` designates the *preferred* alignment, not the *required* alignment.) This is in stark contrast with `repr(C)` in Rust, which always lays out fields at their "natural alignment". Any fix for this would require splitting up `repr(C)` since anyone in case 2 cannot tolerate under-aligned fields (since it would disallow taking references to those fields). # Guide-level explanation From 7d7b01a1fda9635ec744c37d56def69561d3fcad Mon Sep 17 00:00:00 2001 From: RustyYato Date: Wed, 3 Sep 2025 14:26:20 -0700 Subject: [PATCH 41/61] update motivation section for AIX --- text/3845-repr-ordered-fields.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 0cbba9251c1..c61238154a8 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -57,11 +57,6 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). -Fixing the layout of this struct will also most likely let us remove a long-standing hack from our implementation of the MSVC ABI: -unlike all other ABIs, we do *not* entirely skip ZST with that ABI but instead mark them to be passed by-ptr. -This is needed to correctly pass types like `SomeFFI`. -If we fix our layout computation to match that of MSVC, we no longer need this special case in the ABI logic. - ## RFC #3718 This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. @@ -89,7 +84,7 @@ Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). -In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) +In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes (since the first field has a preferred alignment of 8 bytes). ```C struct Floats { @@ -103,10 +98,10 @@ This is because > In aggregates, the first member of this data type is aligned according to its natural alignment value; subsequent members of the aggregate are aligned on 4-byte boundaries. > - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1) -Even though on AIX `__alignof__(double)` is 8, it is still laid out an a 4-byte boundary. -(That's no contradiction since `__alignof__` designates the *preferred* alignment, not the *required* alignment.) +On AIX `__alignof__(double)` is 8, but field `c` is laid out at a 4-byte boundary. This is fine because `__alignof__` designates the *preferred* alignment, not the *required* alignment. Note that in Rust, we only ever use the *required* alignment and don't have a concept of a *preferred* alignment. So in Rust, we have designated the alignment of f64 to be 8 bytes. + +Any fix for this would require splitting up `repr(C)`, since reducing the alignment of `f64` would reduce the size of `Floats` from `24` to `20`, which also doesn't match `C`, and we cannot special case the alignment of `Floats` to be larger since that doesn't match the algorithm currently specified for `repr(C)` (making it a breaking change). -This is in stark contrast with `repr(C)` in Rust, which always lays out fields at their "natural alignment". Any fix for this would require splitting up `repr(C)` since anyone in case 2 cannot tolerate under-aligned fields (since it would disallow taking references to those fields). # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From 81a1a42bb68dc19c91d1507db9f0086baa73b277 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 5 Sep 2025 12:44:23 -0700 Subject: [PATCH 42/61] Update AIX section and minor rewording throughout --- text/3845-repr-ordered-fields.md | 34 ++++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index c61238154a8..87ec8780d0d 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -67,11 +67,11 @@ By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, pac Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). -The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8-bytes, and may instead only align them to 4 bytes. +The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8 bytes, and may instead only align them to 4 bytes. -Any proper work around will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. +Any proper workaround will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. -For AIX, the issue is that `f64` is treated as aligned to 4-bytes if it is not the first field in a struct. i.e. +For AIX, the issue is that `f64` is treated as aligned to 4 bytes if it is not the first field in a struct. i.e. ```C struct Foo { char a; @@ -84,7 +84,7 @@ Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). -In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes (since the first field has a preferred alignment of 8 bytes). +In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes. Since the first field has a natural alignment of 8 bytes - AKA the size is 8 bytes. ```C struct Floats { @@ -95,10 +95,10 @@ struct Floats { ``` This is because -> In aggregates, the first member of this data type is aligned according to its natural alignment value; subsequent members of the aggregate are aligned on 4-byte boundaries. -> - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1) +> In aggregates, the first member of this data type is aligned according to its natural alignment \[its size\] value; subsequent members of the aggregate are aligned on 4-byte boundaries. +> - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1, which applies to `double` and `long double` data types) -On AIX `__alignof__(double)` is 8, but field `c` is laid out at a 4-byte boundary. This is fine because `__alignof__` designates the *preferred* alignment, not the *required* alignment. Note that in Rust, we only ever use the *required* alignment and don't have a concept of a *preferred* alignment. So in Rust, we have designated the alignment of f64 to be 8 bytes. +On AIX `__alignof__(double)` is 8, but field `c` is laid out at a 4-byte boundary. This is fine because `__alignof__` designates the *preferred* alignment, not the *required* alignment. Note that in Rust, we only ever use the *required* alignment and don't have a concept of a *preferred* alignment. So in Rust, we have designated the alignment of `f64` to be 8 bytes. Any fix for this would require splitting up `repr(C)`, since reducing the alignment of `f64` would reduce the size of `Floats` from `24` to `20`, which also doesn't match `C`, and we cannot special case the alignment of `Floats` to be larger since that doesn't match the algorithm currently specified for `repr(C)` (making it a breaking change). @@ -125,9 +125,9 @@ warning: use of `repr(C)` in type `Foo` Using `repr(C)` on all editions (including > 2024) when there are no extern blocks or functions in the crate will trigger a warn-by-default lint suggesting to use `repr(ordered_fields)`. Since the most likely reason to do this is if you haven't heard of `repr(ordered_fields)` or are upgrading to the most recent Rust version (which now contains `repr(ordered_fields)`). -If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. +If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way, we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. -This does miss one potential use-case, where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. This should be an extremely small minority of crates, and they can silence this warning crate-wide. +This does miss one potential use case, where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. This should be an extremely small minority of crates, and they can silence this warning crate-wide. The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. @@ -159,7 +159,7 @@ This makes it easier to tell *why* the `repr` was applied to a given struct. If The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). ## `repr(ordered_fields)` -> The `ordered_fields` representation is designed for one purpose: create types that you can soundly perform operations on that rely on data layout such as reinterpreting values as a different type +> The `ordered_fields` representation is designed for one purpose: to create types that you can soundly perform operations on that rely on data layout, such as reinterpreting values as a different type > > This representation can be applied to structs, unions, and enums. > @@ -202,10 +202,10 @@ union FooUnion { `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum -The enum's tag type is same type that is used for `repr(C)` in edition <= 2024, and the discriminants is assigned the same was as `repr(C)` (in edition <= 2024). This means the discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. -This does mean that the tag type will be platform specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). This warning should suggest the smallest integer type which can hold the discriminant values (preferring signed integers to break ties). +The enum's tag type is the same type that is used for `repr(C)` in edition <= 2024, and the discriminants are assigned the same way as `repr(C)` (in edition <= 2024). This means the discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. +This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). -If an enum doesn't have any fields, then it is represented exactly by it's discriminant. +If an enum doesn't have any fields, then it is represented exactly by its discriminant. ```rust // tag = i16 // represented as i16 @@ -317,11 +317,11 @@ fn get_layout_for_union(field_layouts: &[Layout]) -> Result } /// Takes in the layout of each variant (and their fields) (in declaration order), and returns the layout of the entire enum -/// the offsets of all fields of the enum is left as an exercise for the readers +/// the offsets of all fields of the enum are left as an exercise for the readers /// NOTE: the enum tag is always at offset 0 fn get_layout_for_enum( // the discriminants may be negative for some enums - // or u128::MAX for some enums, so there is no one primitive integer type which works. So BigInteger + // or u128::MAX for some enums, so there is no one primitive integer type that works. So BigInteger discriminants: &[BigInteger], variant_layouts: &[&[Layout]] ) -> Result { @@ -397,11 +397,11 @@ See Rationale and Alternatives as well * something else? * Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? * Should unions expose some niches? - * For example, if all variants of the union are structs which have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making union of structs behave more like an enum). + * For example, if all variants of the union are structs that have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making a union of structs behave more like an enum). * This must be answered before stabilization, as it is set in stone after that * Should this `repr` be versioned? * This way we can evolve the repr (for example, by adding new niches) -* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change, but at that point it will likely only be breaking code no one uses. +* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. * Leaning towards no * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type From 98be259d99e99b66f14c21eb9bfcfd6733c72853 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Mon, 26 Jan 2026 18:29:33 -0600 Subject: [PATCH 43/61] some clarifications about the spec clarify that we are describing the behavior of repr(ordered_fields) clarify differences with `repr(iN)` --- text/3845-repr-ordered-fields.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 87ec8780d0d..882332ef409 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -165,7 +165,7 @@ The exact algorithm is deferred to whatever the default target C compiler does w > > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` ### struct -Structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. +When applying `repr(ordered_fields)` structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. The alignment of a struct is the same as the alignment of the most aligned field. ```rust @@ -186,7 +186,10 @@ Would be laid out in memory like so a...bbbbcc..dddd ``` ### union -Unions would be laid out with the same size as their largest field, and the same alignment as their most aligned field. +When applying `repr(ordered_fields)` to an unions would be laid out as followed: +* the same size as their largest field +* the same alignment as their most aligned field +* all fields are at offset 0 ```rust // assuming that u32 is aligned to 4 bytes @@ -202,8 +205,14 @@ union FooUnion { `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. ### enum -The enum's tag type is the same type that is used for `repr(C)` in edition <= 2024, and the discriminants are assigned the same way as `repr(C)` (in edition <= 2024). This means the discriminants are assigned such that each variant without an explicit discriminant is exactly one more than the previous variant in declaration order. -This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). +When applying `repr(ordered_fields)` to an enum, the enum's tag type and discriminant will be the same as when applying `repr(C)` to the enum in edition <= 2024. +This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). +This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). + +For discriminants, this means that it will follow the given algorithm for each variant in declaration order of the variants: +* if a variant has an explicit discriminant value, then that value is assigned +* else if this is the first variant in declaration order, then the discriminant is zero +* else the discriminant value is one more than the previous variant's discriminant (in declaration order) If an enum doesn't have any fields, then it is represented exactly by its discriminant. ```rust @@ -229,6 +238,8 @@ enum FooEnumUnsigned { ``` Enums with fields will be laid out as if they were a struct containing the tag and a union of structs containing the data. +NOTE: This is different from `repr(iN)`/`repr(uN)` which are laid out as a union of structs, where the first field of the struct is the tag. +These two layouts are *NOT* compatible, and adding `repr(ordered_fields)` to `repr(iN)`/`repr(uN)` changes the layout of the enum! For example, this would be laid out the same as the union below ```rust From 3f41cb3dcb2cfd877ca9ace1712f0a77959bffda Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 27 Jan 2026 08:00:48 -0600 Subject: [PATCH 44/61] Apply suggestions from code review Co-authored-by: teor Co-authored-by: Jacob Lifshay --- text/3845-repr-ordered-fields.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 882332ef409..f9d8926f348 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -69,7 +69,7 @@ Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8 bytes, and may instead only align them to 4 bytes. -Any proper workaround will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting what `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. +Any proper workaround will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. For AIX, the issue is that `f64` is treated as aligned to 4 bytes if it is not the first field in a struct. i.e. ```C @@ -84,7 +84,7 @@ Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). -In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes. Since the first field has a natural alignment of 8 bytes - AKA the size is 8 bytes. +In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes. Since the first field has a natural alignment of 8 bytes - AKA its size is 8 bytes. ```C struct Floats { @@ -186,7 +186,7 @@ Would be laid out in memory like so a...bbbbcc..dddd ``` ### union -When applying `repr(ordered_fields)` to an unions would be laid out as followed: +When applying `repr(ordered_fields)`, unions would be laid out as follows: * the same size as their largest field * the same alignment as their most aligned field * all fields are at offset 0 @@ -403,6 +403,7 @@ See Rationale and Alternatives as well * `repr(linear)` * `repr(ordered)` * `repr(sequential)` + * `repr(serial)` * `repr(consistent)` * `repr(declaration_order)` * something else? From 87801bac5db7b9b16f823f4c2ea5ff69bd60a84f Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 27 Jan 2026 17:03:32 -0600 Subject: [PATCH 45/61] Apply suggestion from code review Co-authored-by: Jules Bertholet --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index f9d8926f348..5047639cc47 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -187,8 +187,8 @@ a...bbbbcc..dddd ``` ### union When applying `repr(ordered_fields)`, unions would be laid out as follows: -* the same size as their largest field * the same alignment as their most aligned field +* the same size as their largest field, rounded up to the next multiple of the union's alignment * all fields are at offset 0 ```rust From da8b01875d74b794f70eb1e068912ec22f63bc16 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 31 Jan 2026 14:22:12 +0100 Subject: [PATCH 46/61] edit motivation section --- text/3845-repr-ordered-fields.md | 102 +++++++++++++++++-------------- 1 file changed, 56 insertions(+), 46 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 5047639cc47..1f40fb0b9d4 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -16,15 +16,24 @@ Introduce two new warnings # Motivation [motivation]: #motivation -Currently `repr(C)` serves two roles +Currently `repr(C)` serves two roles: 1. Provide a consistent, cross-platform, predictable layout for a given type 2. Match the target C compiler's struct/union layout algorithm and ABI -But in some cases, these two cases are in tension due to platform weirdness (even on major platforms like Windows MSVC) -* https://github.com/rust-lang/unsafe-code-guidelines/issues/521 -* https://github.com/rust-lang/rust/issues/81996 +But in some cases, these two roles are in tension due to platform's C layout not matching the [simple, general algorithm](https://doc.rust-lang.org/stable/reference/type-layout.html#r-layout.repr.c.struct.size-field-offset) Rust always uses: +* On MSVC, a struct with a single field that is a zero-sized array has size 1. (https://github.com/rust-lang/rust/issues/81996) +* On MSVC, a `repr(C, packed)` with `repr(align)` fields has special behavior: the inner `repr(align)` takes priority, so fields can be highly aligned even in a packed struct. (https://github.com/rust-lang/rust/issues/100743) + (Rust tries to avoid this by forbidding `repr(align)` fields in `repr(packed)` structs, but that check is easily bypassed with generics.) +* On MSVC-x32, `u64` fields are 8-aligned in structs, but the type is only 4-aligned when allocated on the stack. (https://github.com/rust-lang/rust/issues/112480) +* On AIX, `f64` fields sometimes but not always get 8-aligned in structs. (https://github.com/rust-lang/rust/issues/151910) -Code in case 1 generally falls into one of these buckets: +These are all niche cases, but they add up. +Furthermore, it is just fundamentally wrong for Rust to claim that `repr(C)` layout matches C code for the same target while also specifying an exact algorithm for `repr(C)`: +the C standard does not prescribe any particular struct layout, so any target/ABI is in principle free to come up with whatever bespoke rules they like. + +However, fixing this is hard because of (unsafe) code that relies on the other role of `repr(C)`, giving a deterministic layout. +We therefore cannot just "fix" `repr(C)`, we need some sort of transition plan. +This code generally falls into one of these buckets: * rely on the exact layout being consistent across platforms * for example, zero-copy deserialization (see [rkyv](https://crates.io/crates/rkyv)) * manually calculating the offsets of fields @@ -34,20 +43,17 @@ Code in case 1 generally falls into one of these buckets: * match layouts of two different types (or even, two different monomorphizations of the same generic type) * see [here](https://github.com/rust-lang/rust/pull/68099), where in `alloc` this is done for `Rc` and `Arc` to give a consistent layout for all `T` -So, providing any fix for case 2 would subtly break any users of case 1. This breakage cannot be checked easily since it affects unsafe code making assumptions about data layouts. Making it difficult to fix within a single edition/existing editions. - -Here are some examples of the tension and some other RFCs which could benefit from splitting up `repr(C)`'s two cases. +So, providing any fix for role 2 of `repr(C)` would subtly break any users of role 1. +This breakage cannot be checked easily since it affects unsafe code making assumptions about data layouts, making it difficult to fix within a single edition/existing editions. -1. Windows MSVC ZSTs -2. the RFC [#3718](https://github.com/rust-lang/rfcs/pull/3718) for `repr(C, packed(N))` containing overaligned fields -3. A Windows MSVC bug -4. An [AIX](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594) layout bug +### Layout issues -Examples 3 and 4 cannot be solved with this RFC alone, but any fix would require splitting up `repr(C)`. +Before we delve into the proposed solution, we go into a little more detail about the aforementioned platform layout issues. +Some of them cannot be solved with this RFC alone, but all of them have require some approach to split up the two roles of `repr(C)`. -## MSVC ZST +## MSVC: zero-length arrays -As an example of this tension: on Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) +On Windows MSVC, `repr(C)` doesn't always match what MSVC does for ZST structs (see this [issue](https://github.com/rust-lang/rust/issues/81996) for more details) ```rust // should have size 8, but has size 0 @@ -57,50 +63,54 @@ struct SomeFFI([i64; 0]); Of course, making `SomeFFI` size 8 doesn't work for anyone using `repr(C)` for case 1. They want it to be size 0 (as it currently is). -## RFC #3718 - -This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants allow fields which are `align(M)` (while making the `repr(C, ...)` struct less packed). This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. +## MSVC: `repr(align)` inside `repr(packed)` -By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. Thus keeping the Rust-only case free of warts, without compromising on FFI use-cases[1](#ordered_fields_align). +This also plays a role in [#3718](https://github.com/rust-lang/rfcs/pull/3718), where `repr(C, packed(N))` wants to allow fields which are `align(M)`. +On most targets, and with Rust's `repr(C, packed)` specification, the `packed` takes precedence: -## MSVC bug +```rust +#[repr(C, align(8))] +struct I(u8); + +#[repr(C, packed)] +struct O { + // At offset 0 + f1: u8, + // At offset 1 + f2: I, +} +``` -Splitting `repr(C)` also allows making progress on a workaround for the MSVC bug [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). +However, MSVC will put `f2` at offset 8, so arguably that is what `repr(C, packed)` should do on that target. +This is a footgun for normal uses of `repr(packed)`, so it would be better to relegate this strictly to the FFI use-case. However, since `repr(C)` plays two roles, this is difficult. -The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8 bytes, and may instead only align them to 4 bytes. +By splitting `repr(ordered_fields)` off of `repr(C)`, we can allow `repr(C, packed(N))` to contain over-aligned fields (while making the struct less packed), and (continuing to) disallow `repr(ordered_fields, packed(N))` from containing aligned fields. This keeps the Rust-only case free of warts without compromising on FFI use-cases[1](#ordered_fields_align). -Any proper workaround will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. +## MSVC-x32: u64 alignment -For AIX, the issue is that `f64` is treated as aligned to 4 bytes if it is not the first field in a struct. i.e. -```C -struct Foo { - char a; - double b; -} -``` -Field `b` would be laid out at offset 4, which is under-aligned (since `f64` has alignment 8 in Rust). Again, any proper workaround will require reducing the alignment of `f64`, and adjusting `repr(C)`. +Splitting `repr(C)` also allows making progress on dealing with the MSVC "quirk" [rust-lang/rust/112480](https://github.com/rust-lang/rust/issues/112480). -## AIX layout bug +The issue here is that MSVC is inconsistent about the alignment of `u64`/`i64` (and possibly `f64`). In MSVC, the alignment of `u64`/`i64` is reported to be 8 bytes by `alignof` and is correctly aligned in structs. However, when placed on the stack, MSVC doesn't ensure that they are aligned to 8 bytes, and may instead only align them to 4 bytes. +Our interpretation of this behavior is that `alignof` reports the *preferred* alignment (rather than the required alignment) for the type, and MSVC chooses to sometimes overalign `u64` fields in structs. -For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). +No matter the reason for this behavior, any proper solution to this issue will require reducing the alignment of `u64`/`i64` to 4 bytes, and adjusting `repr(C)` to treat `u64`/`i64`'s alignment as 8 bytes. This way, if you have references/pointers to `u64`/`i64` (for example, as out pointers), then the Rust side will not break when the C side passes a 4-byte aligned pointer (but not 8-byte aligned). This could happen if the C side put the integer on the stack, or was manually allocated at some 4-byte alignment. -In AIX, the following struct `Floats` has the following field offsets: `[0, 8, 12]` (in bytes) and a size of 24 bytes. Since the first field has a natural alignment of 8 bytes - AKA its size is 8 bytes. +## AIX: f64 alignment -```C +For AIX, the issue is that `f64` is sometimes treated as aligned to 8 bytes and sometimes as aligned to 4 bytes (the comments indicate the desired layout as computed by a C compiler): +```rust +// Size: 24 +#[repr(C)] struct Floats { - double a; - char b; - double c; -}; + a: f64, // at offset 0 + b: u8, // at offset 8 + c: f64, // at offset 12 +} ``` +There is no way to obtain such a layout using Rust's `repr(C)` layout algorithm. +For more details, see this discussion on [irlo](https://internals.rust-lang.org/t/repr-c-aix-struct-alignment/21594/3). -This is because -> In aggregates, the first member of this data type is aligned according to its natural alignment \[its size\] value; subsequent members of the aggregate are aligned on 4-byte boundaries. -> - [IBM Documentation](https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=data-using-alignment-modes) (Table 1, Note 1, which applies to `double` and `long double` data types) - -On AIX `__alignof__(double)` is 8, but field `c` is laid out at a 4-byte boundary. This is fine because `__alignof__` designates the *preferred* alignment, not the *required* alignment. Note that in Rust, we only ever use the *required* alignment and don't have a concept of a *preferred* alignment. So in Rust, we have designated the alignment of `f64` to be 8 bytes. - -Any fix for this would require splitting up `repr(C)`, since reducing the alignment of `f64` would reduce the size of `Floats` from `24` to `20`, which also doesn't match `C`, and we cannot special case the alignment of `Floats` to be larger since that doesn't match the algorithm currently specified for `repr(C)` (making it a breaking change). +Any fix for this requires splitting up `repr(C)`. # Guide-level explanation [guide-level-explanation]: #guide-level-explanation From ee162411031af96b9fa7955e0c401913d8e50a79 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 7 Apr 2026 17:52:47 -0500 Subject: [PATCH 47/61] reorganize unresolved questions --- text/3845-repr-ordered-fields.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 1f40fb0b9d4..5a95cbaaddc 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -409,30 +409,30 @@ See Rationale and Alternatives as well * The migration plan, as a whole, needs to be ironed out * Currently, it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. -* The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. - * `repr(linear)` - * `repr(ordered)` - * `repr(sequential)` - * `repr(serial)` - * `repr(consistent)` - * `repr(declaration_order)` - * something else? +* Should this `repr` be versioned? + * This way we can evolve the repr (for example, by adding new niches) * Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? +* What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? + * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 +* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033 or some other later edition? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. + * Leaning towards no * Should unions expose some niches? * For example, if all variants of the union are structs that have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making a union of structs behave more like an enum). * This must be answered before stabilization, as it is set in stone after that -* Should this `repr` be versioned? - * This way we can evolve the repr (for example, by adding new niches) -* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. - * Leaning towards no * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) * Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) -* What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? - * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 +* The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. + * `repr(linear)` + * `repr(ordered)` + * `repr(sequential)` + * `repr(serial)` + * `repr(consistent)` + * `repr(declaration_order)` + * something else? # Future possibilities [future-possibilities]: #future-possibilities From a1ea5aed925939d4bad31a874dc5487ddcd3230c Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 7 Apr 2026 17:53:40 -0500 Subject: [PATCH 48/61] minor formatting updates --- text/3845-repr-ordered-fields.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 5a95cbaaddc..63111560843 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -155,6 +155,7 @@ warning: use of `repr(C)` in type `Foo` After enough time has passed, and the community has switched over: This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation @@ -167,6 +168,7 @@ This makes it easier to tell *why* the `repr` was applied to a given struct. If > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). + ## `repr(ordered_fields)` > The `ordered_fields` representation is designed for one purpose: to create types that you can soundly perform operations on that rely on data layout, such as reinterpreting values as a different type @@ -174,7 +176,9 @@ The exact algorithm is deferred to whatever the default target C compiler does w > This representation can be applied to structs, unions, and enums. > > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` + ### struct + When applying `repr(ordered_fields)` structs are laid out in memory in declaration order, with padding bytes added as necessary to preserve alignment. The alignment of a struct is the same as the alignment of the most aligned field. @@ -195,7 +199,9 @@ Would be laid out in memory like so ``` a...bbbbcc..dddd ``` + ### union + When applying `repr(ordered_fields)`, unions would be laid out as follows: * the same alignment as their most aligned field * the same size as their largest field, rounded up to the next multiple of the union's alignment @@ -214,7 +220,9 @@ union FooUnion { ``` `FooUnion` has the same layout as `u32`, since `u32` has both the biggest size and alignment. + ### enum + When applying `repr(ordered_fields)` to an enum, the enum's tag type and discriminant will be the same as when applying `repr(C)` to the enum in edition <= 2024. This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). @@ -364,6 +372,7 @@ fn get_layout_for_enum( Ok(layout) } ``` + ### Migration to `repr(ordered_fields)` The migration will be handled as follows: @@ -396,6 +405,7 @@ The migration will be handled as follows: * This one is currently stuck due to a larger scope than this RFC * do nothing * We keep getting bug reports on Windows (and other platforms), where `repr(C)` doesn't actually match the target C compiler, or we break a bunch of subtle unsafe code to match the target C compiler. + # Prior art [prior-art]: #prior-art @@ -433,6 +443,7 @@ See Rationale and Alternatives as well * `repr(consistent)` * `repr(declaration_order)` * something else? + # Future possibilities [future-possibilities]: #future-possibilities From 5234c1d903d312a8305a821243a3567a82a67e52 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Tue, 7 Apr 2026 18:05:02 -0500 Subject: [PATCH 49/61] add a discussion link for an unresolved question reorg questions again --- text/3845-repr-ordered-fields.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 63111560843..9c0cd924671 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -421,20 +421,21 @@ See Rationale and Alternatives as well * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. * Should this `repr` be versioned? * This way we can evolve the repr (for example, by adding new niches) +* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033 or some other later edition? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. + * Leaning towards no * Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? + * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2291506953 * What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 -* Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033 or some other later edition? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. - * Leaning towards no +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). + * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 + * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) * Should unions expose some niches? * For example, if all variants of the union are structs that have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making a union of structs behave more like an enum). * This must be answered before stabilization, as it is set in stone after that * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) -* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). - * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 - * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) * The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. * `repr(linear)` * `repr(ordered)` From 796423a3871598171681b35528ceb214bcfda110 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 08:43:56 -0500 Subject: [PATCH 50/61] update from review * explain suspicious_repr_c lint * update layout calc for enums * remove unresolved question about union niches --- text/3845-repr-ordered-fields.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 9c0cd924671..eefc67ae1b8 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -139,7 +139,7 @@ If *any* extern block or function (including `extern "Rust"`) is used in the cra This does miss one potential use case, where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. This should be an extremely small minority of crates, and they can silence this warning crate-wide. -The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. +The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. It will be triggered whenever there is a `repr(C)` type, but no `extern` blocks/functions in the crate. This is a basic heuristic to reduce noise, since it is unlikely that you want the `C` repr unless you are interacting with `C` or some other language. ``` warning: use of `repr(C)` in type `Foo` @@ -225,7 +225,7 @@ union FooUnion { When applying `repr(ordered_fields)` to an enum, the enum's tag type and discriminant will be the same as when applying `repr(C)` to the enum in edition <= 2024. This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). -This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). +This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). Since this changes the layout of the type, the warning should mention that the layout will change. For discriminants, this means that it will follow the given algorithm for each variant in declaration order of the variants: * if a variant has an explicit discriminant value, then that value is assigned @@ -356,14 +356,17 @@ fn get_layout_for_enum( ) -> Result { assert_eq!(discriminants.len(), variant_layouts.len()); - let variant_data_layout = variant_layouts.iter() - .try_fold( - Layout::new::<()>(), - |acc, variant_layout| Ok(layout_max(acc, get_layout_for_struct(variant_layout)?.1)?) - )?; - + let variant_data_layouts = variant_layouts.iter() + // each variant's fields are represented as a struct + .map(|variant_fields_layout| get_layout_for_struct(variant_fields_layout).map(|x| x.1)) + .collect::, _>>()?; + + // then the set of all variants is represented as a union + let variant_data_layout = get_layout_for_union(variant_data_layouts)?; + let tag_layout = get_layout_for_tag(discriminants); + // the tag is then prepended to that union let (_, layout) = get_layout_for_struct(&[ tag_layout, variant_data_layout @@ -430,7 +433,7 @@ See Rationale and Alternatives as well * Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) -* Should unions expose some niches? +* ~~Should unions expose some niches?~~ [no](https://github.com/rust-lang/rfcs/pull/3845#discussion_r3088073911) * For example, if all variants of the union are structs that have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making a union of structs behave more like an enum). * This must be answered before stabilization, as it is set in stone after that * Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) From 785e14c6df0a7a8f996950248dc762b2142c0f36 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 09:36:35 -0500 Subject: [PATCH 51/61] first draft after lang-team meeting --- text/3845-repr-ordered-fields.md | 95 ++++++++++++++++++++++---------- 1 file changed, 66 insertions(+), 29 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index eefc67ae1b8..35643cc7375 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -6,12 +6,28 @@ # Summary [summary]: #summary -Introduce a new `repr` (let's call it `repr(ordered_fields)`, but that can be bikeshedded if this RFC is accepted) that can be applied to `struct`, `enum`, and `union` types, which guarantees a simple and predictable layout. Then provide an initial migration plan to switch users from `repr(C)` to `repr(ordered_fields)`. This allows restricting the meaning of `repr(C)` to just serve the FFI use-case. +Introduce a few new `repr`s (all names can be bikeshedded if this RFC is accepted): +* `repr(ordered_fields)` +* `repr(C#editionCurr)` +* `repr(C#editionNext)` -Introduce two new warnings -1. An edition migration warning, when updating to the next edition, that the meaning of `repr(C)` is changing -2. A warn-by-default lint when `repr(ordered_fields)` is used on enums without the tag type specified. Since this is likely not what the user wanted +And the meaning of `repr(C)` will change in the next edition (presumably `Edition 2027`). All the new reprs can be applied to `struct`, `enum`, and `union` types. + +`repr(ordered_fields)` provides a simple, predicable in memory layout for types. This RFC does *NOT* specify a stable ABI for `repr(ordered_fields)` - that should either be handled by a future RFC or another `repr`. +Layout-wise, this is the same as `repr(C)` on all current editions where both compile. + +`repr(C#editionCurr)` is the same as `repr(C)` on all current editions. This will preserve the same layout and ABI as `repr(C)` on current editions. This repr is mainly targeted for use during the transition time. This way we can do an automated fix for `repr(C)` -> `repr(C#editionCurr)`, and you will know that this was done by an automated fix. If we used `repr(ordered_fields)` for this purpose, then it would be ambiguous if that was intentional or automated. + +`repr(C#editionNext)` is the same as `repr(C)` on the next edition. This repr is mainly targeted for use during the transition time. This `repr` should *only* be used for FFI. This serves the dual purpose of `repr(C#editionCurr)`, it allows piecewise migration to the new edition while staying on the old edition. + +`repr(C)`, this will be defined as the same representation as the platform C compiler, as specified by the target-triple. This `repr` should *only* be used for FFI. + +Introduce a few new warnings and errors +1. A error when `repr(ordered_fields)` is used on enums without the tag type specified. +2. An edition migration warning, when updating to the next edition, that the meaning of `repr(C)` is changing. This will have an automated fix to `repr(C#editionCurr)` 3. A warn-by-default lint when `repr(C)` is used, and there are no `extern` blocks or functions in the crate (on all editions) +4. An idiom lint when `repr(C#editionNext)` is used on the next edition, with an automated fix to `repr(C)`. +5. An idiom lint when `repr(C#editionCurr)` is used on the next edition. No automated fix is provided, instead the lint should guide users to choose between `repr(C)` or `repr(ordered_fields)` # Motivation [motivation]: #motivation @@ -117,29 +133,40 @@ Any fix for this requires splitting up `repr(C)`. `repr(ordered_fields)` is a new representation that can be applied to `struct`, `enum`, and `union` to give them a consistent, cross-platform, and predictable in-memory layout. -`repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target for structs, unions, and field-less enums. Enums with fields will be laid out as if they are a union of structs with the corresponding fields. +`repr(C)` in edition <= 2024 is an alias for `repr(ordered_fields)` and in all other editions, it matches the default C compiler for the given target triple for structs, unions, and field-less enums. Enums with fields are laid out as a struct containing a tag and payload. With the payload being a union of structs of all the variants. (This is how they are currently laid out in `repr(C)`) + +Using `repr(C)` in editions <= 2024 triggers a lint (seen below) as an edition migration compatibility lint with a machine-applicable fix that switches it to `repr(C#editionCurr)`. +* If you are using `repr(C)` for FFI, then you should switch to `repr(C#editionNext)` +* If you are not using `repr(C)` for FFI, then you should switch to `repr(ordered_fields)` -Using `repr(C)` in editions <= 2024 triggers a lint to use `repr(ordered_fields)` as an edition migration compatibility lint with a machine-applicable fix. If you are using `repr(C)` for FFI, then you may silence this lint. If you are using `repr(C)` for anything else, please switch over to `repr(ordered_fields)` so updating to future editions doesn't change the meaning of your code. +The machine-applicable fix is provided to allow users to do migrations on their own terms. This way a user can do `cargo fix`, update the edition themselves, then fix uses of `repr(C#editionCurr)` in the new edition. ``` warning: use of `repr(C)` in type `Foo` --> src/main.rs:14:10 | 14 | #[repr(C)] - | ^^^^^^^ help: consider switching to `repr(ordered_fields)` + | ^^^^^^^ help: consider switching to `repr(C#editionNext)` or `repr(ordered_fields)` | struct Foo { | = note: `#[warn(edition_2024_repr_c)]` on by default - = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(ordered_fields)` + = note: `repr(C)` is planned to change meaning in the next edition to match the target platform's layout algorithm. This may change the layout of this type on certain platforms. To keep the current layout, switch to `repr(C#editionNext)` or `repr(ordered_fields)` ``` -Using `repr(C)` on all editions (including > 2024) when there are no extern blocks or functions in the crate will trigger a warn-by-default lint suggesting to use `repr(ordered_fields)`. Since the most likely reason to do this is if you haven't heard of `repr(ordered_fields)` or are upgrading to the most recent Rust version (which now contains `repr(ordered_fields)`). +Using `repr(C)` on all editions (including > 2024) when there are no extern blocks or functions in the crate will trigger a allow-by-default lint (`suspicious_repr_c`) suggesting to use `repr(ordered_fields)`. + +This is allow by default, since the edition lint should do the heavy lifting. This reduces the noise, and provides a tool for interested users to reduce their reliance on `repr(C)` when it is probably not needed. -If *any* extern block or function (including `extern "Rust"`) is used in the crate, then this lint will not be triggered. This way, we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. +If *any* extern block or function (including `extern "Rust"`) is used in the crate, then the `suspicious_repr_c` lint will not be triggered. This way, we don't have too many false positives for this lint. However, the lint should *not* suggest adding a `extern` block or function, since the problem is likely the `repr`. -This does miss one potential use case, where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. This should be an extremely small minority of crates, and they can silence this warning crate-wide. +This does miss some potential use cases +1. where a crate provides a suite of FFI-capable types, but does not actually provide any `extern` functions or blocks. +2. the crate wants to interact with hardware, and using `repr(C)` is the correct repr +3. the crate wants is using shared memory with another process, and using `repr(C)` is the correct repr. -The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c`. It will be triggered whenever there is a `repr(C)` type, but no `extern` blocks/functions in the crate. This is a basic heuristic to reduce noise, since it is unlikely that you want the `C` repr unless you are interacting with `C` or some other language. +Since this is an allow-by-default lint, I think this is fine. + +The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c` (i.e. `edition_2024_repr_c` shouldn't be emitted if `suspicious_repr_c` is emitted). ``` warning: use of `repr(C)` in type `Foo` @@ -150,7 +177,7 @@ warning: use of `repr(C)` in type `Foo` | struct Foo { | = note: `#[warn(suspicious_repr_c)]` on by default - = note: `repr(C)` is intended for FFI, and since there are no `extern` blocks or functions, it's likely that you meant to use `repr(ordered_fields)` to get a stable and consistent layout for your type + = note: `repr(C)` is intended for FFI, and since there are no `extern` blocks or functions, it's likely that you meant to use `repr(ordered_fields)` to get a stable and consistent layout for your type. ``` After enough time has passed, and the community has switched over: @@ -159,15 +186,25 @@ This makes it easier to tell *why* the `repr` was applied to a given struct. If # Reference-level explanation [reference-level-explanation]: #reference-level-explanation -## `repr(C)` +## `repr(C#editionCurr)` + +see the current Rust reference entry for `repr(C)`: https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation + +## `repr(C#editionNext)` -> The `C` representation is designed for one purpose: creating types that are interoperable with the C Language. +> The `C#editionNext` representation is designed for one purpose: creating types that are interoperable with the C Language. > -> This representation can be applied to structs, unions, and enums. The exception is [zero-variant enums](https://doc.rust-lang.org/stable/reference/items/enumerations.html#zero-variant-enums) for which the `C` representation is an error. +> This representation can be applied to structs, unions, and enums. The exception is [zero-variant enums](https://doc.rust-lang.org/stable/reference/items/enumerations.html#zero-variant-enums) for which the `C#editionNext` representation is an error. > > - edited version of the [reference](https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation) on `repr(C)` -The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). +The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). `rustc` may grow extra flags to control the behavior of `repr(C)`, in order to match certain flags in the default C compiler, however those will need to be their own proposals. This RFC does not specify any extra control over `repr(C)`. + +## `repr(C)` + +This repr's meaning depends on the edition of the crate: +* on editions < `editionNext`, this means `repr(C#editionCurr)` +* on editions >= `editionNext`, this means `repr(C#editionNext)` ## `repr(ordered_fields)` @@ -223,9 +260,7 @@ union FooUnion { ### enum -When applying `repr(ordered_fields)` to an enum, the enum's tag type and discriminant will be the same as when applying `repr(C)` to the enum in edition <= 2024. -This does mean that the tag type will be platform-specific. To alleviate this concern, using `repr(ordered_fields)` on an enum without an explicit `repr(uN)`/`repr(iN)` will trigger a warning (name TBD). -This warning should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). Since this changes the layout of the type, the warning should mention that the layout will change. +When applying `repr(ordered_fields)` to an enum, the tag type must be specified. i.e. `repr(ordered_fields, u8)` or `repr(ordered_fields, i32)`. If a tag type is not specified, then this results in a hard error, which should suggest the smallest integer type that can hold the discriminant values (preferring signed integers to break ties). For discriminants, this means that it will follow the given algorithm for each variant in declaration order of the variants: * if a variant has an explicit discriminant value, then that value is assigned @@ -376,19 +411,21 @@ fn get_layout_for_enum( } ``` -### Migration to `repr(ordered_fields)` +## Migration Plan The migration will be handled as follows: -* after `repr(ordered_fields)` is implemented - * add an edition migration lint for `repr(C)` +* after the reprs outlined in this RFC are implemented + * at this point `repr(C)` and `repr(C#editionCurr)` will have identical behavior + * add an edition migration lint for `repr(C)` (`edition_2024_repr_c`) * this warning should be advertised publicly (maybe on the Rust Blog?), so that as many people use it. Since even if you are staying on edition <= 2024, it is helpful to switch to `repr(ordered_fields)` to make your intentions clearer - * at this point both `repr(ordered_fields)` and `repr(C)` will have identical behavior - * the warning will come with a machine-applicable fix - * Any crate that does not have FFI can just apply the autofix - * Any crate which uses `repr(C)` for FFI can ignore the warning crate-wide - * Any crate that mixes both must do extra work to figure out which is which. (This is likely a tiny minority of crates) -* Once the next edition rolls around (2027?), `repr(C)` on the new edition will *not* warn. Instead, the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. + * add the `suspicious_repr_c` lint to help people migrate away from `repr(C)`. +* Once the next edition rolls around (2027?) + * at this point all of `repr(C)` and `repr(C#editionNext)` will have identical behavior + * `repr(C)` on the new edition will *not* warn. Instead, the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. * The warning for previous editions will continue to be in effect + * The two idiom lints will come into effect to provide an off ramp for `repr(C#editionCurr)` and `repr(C#editionNext)` +* Once the following edition rolls around (2030?) + * `repr(C#editionCurr)` and `repr(C#editionNext)` are no longer valid. You may only use `repr(ordered_fields)` or `repr(C)` # Drawbacks [drawbacks]: #drawbacks From 55e1b893684a555ea15b7a9e717f547ff45685c4 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 09:44:03 -0500 Subject: [PATCH 52/61] add an unresolved question for the edition repr C names --- text/3845-repr-ordered-fields.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 35643cc7375..8fc4cd3e4af 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -484,6 +484,12 @@ See Rationale and Alternatives as well * `repr(consistent)` * `repr(declaration_order)` * something else? +* The name of the new repr `repr(C#editionCurr)` and `repr(C#editionNext)` are bad (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. + * `repr(C#edition2024)`/`repr(C#edition2027)` + * `repr(e24#C)`/`repr(e27#C)` + * `repr(e2024#C)`/`repr(e2027#C)` + * ~~`repr(C24)`/`repr(C27)`~~ - likely too confusing to actual C standards + * something else? # Future possibilities [future-possibilities]: #future-possibilities From 84f28c1684a5c9564401c7639d5470949510c449 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 09:47:45 -0500 Subject: [PATCH 53/61] update unresolved questions --- text/3845-repr-ordered-fields.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 8fc4cd3e4af..45e1c5552a1 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -391,8 +391,8 @@ fn get_layout_for_enum( ) -> Result { assert_eq!(discriminants.len(), variant_layouts.len()); + // each variant's fields are represented as a struct let variant_data_layouts = variant_layouts.iter() - // each variant's fields are represented as a struct .map(|variant_fields_layout| get_layout_for_struct(variant_fields_layout).map(|x| x.1)) .collect::, _>>()?; @@ -459,11 +459,12 @@ See Rationale and Alternatives as well * The migration plan, as a whole, needs to be ironed out * Currently, it is just a sketch, but we need timelines, dates, and guarantees to switch `repr(C)` to match the layout algorithm of the target C compiler. * Before this RFC is accepted, t-compiler will need to commit to fixing the layout algorithm sometime in the next edition. -* Should this `repr` be versioned? +* ~~Should this `repr` be versioned?~~ * This way we can evolve the repr (for example, by adding new niches) + * no need to for now, this can be done as a future proposal * Should we change the meaning of `repr(C)` in editions <= 2024 after we have reached edition 2033 or some other later edition? Yes, it's a breaking change. But at that point, it will likely only be breaking code no one uses. * Leaning towards no -* Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not? +* ~~Is the ABI of `repr(ordered_fields)` specified (making it safe for FFI)? Or not?~~ Not in this RFC * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2291506953 * What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 @@ -473,7 +474,7 @@ See Rationale and Alternatives as well * ~~Should unions expose some niches?~~ [no](https://github.com/rust-lang/rfcs/pull/3845#discussion_r3088073911) * For example, if all variants of the union are structs that have a common prefix, then any niches of that common prefix could be exposed (i.e. in the enum case, making a union of structs behave more like an enum). * This must be answered before stabilization, as it is set in stone after that -* Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`) +* ~~Should we warn on `repr(ordered_fields)` applied to enums when explicit tag type is missing (i.e. no `repr(u8)`/`repr(i32)`)~~ This is now a hard error * Since it's likely they didn't want the same tag type as `C`, and wanted the smallest possible tag type * What should the lints look like? (can be decided after stabilization if needed, but preferably this is hammered out before stabilization and after this RFC is accepted) * The name of the new repr `repr(ordered_fields)` is a mouthful (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. From d95a19103d78a95f6758de500f0229e90a96bb40 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 09:52:50 -0500 Subject: [PATCH 54/61] specify the idiom lints --- text/3845-repr-ordered-fields.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 45e1c5552a1..3f9f25e2e1c 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -183,6 +183,12 @@ warning: use of `repr(C)` in type `Foo` After enough time has passed, and the community has switched over: This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. +The idiom lint `repr_c_curr` will trigger on usages of `repr(C#editionCurr)`. This is allow-by-default on current editions and warn by default on new editions. +On the current edition it will guide people towards `repr(C#editionNext)` or `repr(ordered_fields)`. +On future current editions it will guide people towards `repr(C)` or `repr(ordered_fields)`. + +The idiom lint `repr_c_next` will trigger on usages of `repr(C#editionNext)`. This is allow-by-default on current editions and deny-by-default/warn-by-default in future editions. This comes with a machine applicable fix to switch to `repr(C)`. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation From 34cc2125e0a47c4317430354ae2839fa73fff12b Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 09:58:08 -0500 Subject: [PATCH 55/61] add a note to the migration plan --- text/3845-repr-ordered-fields.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 3f9f25e2e1c..bd58ba4ad38 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -432,6 +432,7 @@ The migration will be handled as follows: * The two idiom lints will come into effect to provide an off ramp for `repr(C#editionCurr)` and `repr(C#editionNext)` * Once the following edition rolls around (2030?) * `repr(C#editionCurr)` and `repr(C#editionNext)` are no longer valid. You may only use `repr(ordered_fields)` or `repr(C)` + * unsure about this step, it's not necessary, but it seems nice to prevent usage of a migration step. # Drawbacks [drawbacks]: #drawbacks From db73f7c22b0d0ae7b12928b29222c8c1e42c447f Mon Sep 17 00:00:00 2001 From: RustyYato Date: Thu, 16 Apr 2026 10:09:13 -0500 Subject: [PATCH 56/61] add a note that Rust team can change `repr(C)` to match target C compiler --- text/3845-repr-ordered-fields.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index bd58ba4ad38..6595a78e60c 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -206,6 +206,8 @@ see the current Rust reference entry for `repr(C)`: https://doc.rust-lang.org/st The exact algorithm is deferred to whatever the default target C compiler does with default settings (or if applicable, the most commonly used settings). `rustc` may grow extra flags to control the behavior of `repr(C)`, in order to match certain flags in the default C compiler, however those will need to be their own proposals. This RFC does not specify any extra control over `repr(C)`. +If any bugs are found (i.e. differences between the target C compiler's layout/ABI and `repr(C)`) then the Rust team reserves the right to change the behavior of `repr(C)` to comply with the target C compiler. + ## `repr(C)` This repr's meaning depends on the edition of the crate: @@ -475,7 +477,7 @@ See Rationale and Alternatives as well * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2291506953 * What should `repr(C)` do when a given type wouldn't compile in the corresponding `C` compiler (like fieldless structs in MSVC)? * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105 -* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (overaligned types). +* Should `repr(ordered_fields, packed(N))` allow `align(M)` types where `M > N` (over-aligned types). * discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319098177 * One option is to allow it and cap those fields to be aligned to `N`. This seems consistent with the handling of other over-aligned types. (i.e. putting a `u32` in a `repr(packed(2))` type) * ~~Should unions expose some niches?~~ [no](https://github.com/rust-lang/rfcs/pull/3845#discussion_r3088073911) From b7eee7a33291f3b758be70953ab090a7df76fa8b Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 17 Apr 2026 08:42:09 -0500 Subject: [PATCH 57/61] add some notes to the reference section to document intent remove section in migration plan about removing the `repr(C#edition*)` reprs add a new spelling to the list --- text/3845-repr-ordered-fields.md | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 6595a78e60c..1b85f044a50 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -6,7 +6,7 @@ # Summary [summary]: #summary -Introduce a few new `repr`s (all names can be bikeshedded if this RFC is accepted): +Introduce a few new `repr`s (all names are placeholders and can be bikeshedded if this RFC is accepted): * `repr(ordered_fields)` * `repr(C#editionCurr)` * `repr(C#editionNext)` @@ -18,7 +18,7 @@ Layout-wise, this is the same as `repr(C)` on all current editions where both co `repr(C#editionCurr)` is the same as `repr(C)` on all current editions. This will preserve the same layout and ABI as `repr(C)` on current editions. This repr is mainly targeted for use during the transition time. This way we can do an automated fix for `repr(C)` -> `repr(C#editionCurr)`, and you will know that this was done by an automated fix. If we used `repr(ordered_fields)` for this purpose, then it would be ambiguous if that was intentional or automated. -`repr(C#editionNext)` is the same as `repr(C)` on the next edition. This repr is mainly targeted for use during the transition time. This `repr` should *only* be used for FFI. This serves the dual purpose of `repr(C#editionCurr)`, it allows piecewise migration to the new edition while staying on the old edition. +`repr(C#editionNext)` is the same as `repr(C)` on the next edition. This repr is mainly targeted for use during the transition time. This `repr` should *only* be used for FFI. This serves the dual purpose of `repr(C#editionCurr)`, it allows piecemeal migration to the new edition while staying on the old edition. `repr(C)`, this will be defined as the same representation as the platform C compiler, as specified by the target-triple. This `repr` should *only* be used for FFI. @@ -166,7 +166,7 @@ This does miss some potential use cases Since this is an allow-by-default lint, I think this is fine. -The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c` (i.e. `edition_2024_repr_c` shouldn't be emitted if `suspicious_repr_c` is emitted). +The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c` (i.e. `edition_2024_repr_c` shouldn't be emitted if `suspicious_repr_c` is emitted to reduce noise). ``` warning: use of `repr(C)` in type `Foo` @@ -180,24 +180,28 @@ warning: use of `repr(C)` in type `Foo` = note: `repr(C)` is intended for FFI, and since there are no `extern` blocks or functions, it's likely that you meant to use `repr(ordered_fields)` to get a stable and consistent layout for your type. ``` -After enough time has passed, and the community has switched over: -This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. - The idiom lint `repr_c_curr` will trigger on usages of `repr(C#editionCurr)`. This is allow-by-default on current editions and warn by default on new editions. On the current edition it will guide people towards `repr(C#editionNext)` or `repr(ordered_fields)`. On future current editions it will guide people towards `repr(C)` or `repr(ordered_fields)`. The idiom lint `repr_c_next` will trigger on usages of `repr(C#editionNext)`. This is allow-by-default on current editions and deny-by-default/warn-by-default in future editions. This comes with a machine applicable fix to switch to `repr(C)`. +After enough time has passed, and the community has switched over: +This makes it easier to tell *why* the `repr` was applied to a given struct. If `repr(C)`, it's about FFI and interop. If `repr(ordered_fields)`, then it's for a dependable layout. + # Reference-level explanation [reference-level-explanation]: #reference-level-explanation ## `repr(C#editionCurr)` -see the current Rust reference entry for `repr(C)`: https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation +Note: This will be identical to `repr(C)` on current editions. + +See the current Rust reference entry for `repr(C)`: https://doc.rust-lang.org/stable/reference/type-layout.html#the-c-representation ## `repr(C#editionNext)` +Note: This will be a consistent way to refer to the fixed `repr(C)`. It is intended to only be used for the migration between editions, however it is possible to use this to have an edition-agnostic way to refer to the fixed `repr(C)`. This second use-case is considered, but will not be prioritized. + > The `C#editionNext` representation is designed for one purpose: creating types that are interoperable with the C Language. > > This representation can be applied to structs, unions, and enums. The exception is [zero-variant enums](https://doc.rust-lang.org/stable/reference/items/enumerations.html#zero-variant-enums) for which the `C#editionNext` representation is an error. @@ -210,12 +214,16 @@ If any bugs are found (i.e. differences between the target C compiler's layout/A ## `repr(C)` +Note: This preserves the nice name of `repr(C)`, and gives it the intended meaning. + This repr's meaning depends on the edition of the crate: * on editions < `editionNext`, this means `repr(C#editionCurr)` * on editions >= `editionNext`, this means `repr(C#editionNext)` ## `repr(ordered_fields)` +Note: This provides a nice name for a Rust-specific, stable, consistent layout. + > The `ordered_fields` representation is designed for one purpose: to create types that you can soundly perform operations on that rely on data layout, such as reinterpreting values as a different type > > This representation can be applied to structs, unions, and enums. @@ -432,9 +440,6 @@ The migration will be handled as follows: * `repr(C)` on the new edition will *not* warn. Instead, the meaning will have changed to mean *only* compatibility with C. The docs should be adjusted to mention this edition wrinkle. * The warning for previous editions will continue to be in effect * The two idiom lints will come into effect to provide an off ramp for `repr(C#editionCurr)` and `repr(C#editionNext)` -* Once the following edition rolls around (2030?) - * `repr(C#editionCurr)` and `repr(C#editionNext)` are no longer valid. You may only use `repr(ordered_fields)` or `repr(C)` - * unsure about this step, it's not necessary, but it seems nice to prevent usage of a migration step. # Drawbacks [drawbacks]: #drawbacks @@ -493,6 +498,7 @@ See Rationale and Alternatives as well * `repr(serial)` * `repr(consistent)` * `repr(declaration_order)` + * `repr(stable)` * something else? * The name of the new repr `repr(C#editionCurr)` and `repr(C#editionNext)` are bad (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. * `repr(C#edition2024)`/`repr(C#edition2027)` From 891522b02c1ce630e5a45ad42abd4522a92dcaf9 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 17 Apr 2026 09:38:26 -0500 Subject: [PATCH 58/61] add migration use-cases and guiding principle --- text/3845-repr-ordered-fields.md | 79 ++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 1b85f044a50..30ffd5ce7bb 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -62,6 +62,18 @@ This code generally falls into one of these buckets: So, providing any fix for role 2 of `repr(C)` would subtly break any users of role 1. This breakage cannot be checked easily since it affects unsafe code making assumptions about data layouts, making it difficult to fix within a single edition/existing editions. +### Guiding principle + +This RFC will require a large migration across the ecosystem. There are two major use-cases that are prioritized in this document +* FFI-only crates, like `*-sys` crates or crates that expose a `C` interface + * these crates should ideally have to do no work. They want the fixed `repr(C)` +* Pure Rust crates that don't rely on the C calling convention + * This is for role 2, they typically don't want their layout changing from under them. So switching to `repr(ordered_fields)` will be the correct fix, with one exception: `enum`s with fields. This is likely a very small minority, since using `repr(C)` on an enum with fields is very niche, esp. because it's easy to get UB when using Rust enums with FFI. + +There are a number of other use-cases which aren't put on a pedestal like these two. Many of them are detailed near the [end](#migration-examples) of this document. + +For these other use-cases, this RFC should make it possible to upgrade to the new edition and get the behavior you want. But it may require more work or it may not look as pretty (after all the bikeshedding for this RFC is done). + ### Layout issues Before we delve into the proposed solution, we go into a little more detail about the aforementioned platform layout issues. @@ -441,6 +453,73 @@ The migration will be handled as follows: * The warning for previous editions will continue to be in effect * The two idiom lints will come into effect to provide an off ramp for `repr(C#editionCurr)` and `repr(C#editionNext)` +### Migration Examples + +In this section, we'll go over a few different crate archetypes, and one possible migration timeline for them. This is *not* an intended migration plan, forced migration plan, or anything of that nature. This is only to provide examples that show what archetypes were considered when designing this plan. + +They core here is to minimize the work needed to for the migration across a wide variety of types of crates. The biggest priority is ensuring that FFI-only crates don't have to do any work, and if you only have non-FFI use-cases it should be almost as simple as find/replace + +#### `*-sys` crate + +These crates typically only have `extern` blocks and types. They are universally only for FFI, so the migration plan is simple. + +* update edition to the next edition - no changes required + +#### crates exposing a C interface (only FFI usages of `repr(C)`) + +These are crates written in Rust, but expose an interface to be called from another language. + +* update edition to the next edition - no changes required + +#### crates help build a FFI interface + +This includes crates like `bindgen`, `cxx`, `pyo3`, `jni`, or `uniffi` which help build FFI interfaces. + +Depending on the tool, they may or may not be edition-aware. If they are edition aware, then they can use `repr(C#editionNext)` or `repr(C)` to get the fixed `repr(C)` depending on the edition. + +If they are not edition aware, then they may migrate to using `repr(C#editionNext)` exclusively to ensure they get the fixed `repr(C)` on all editions. + +#### crates using `repr(C)` purely for stable layout (no stable calling convention required) + +If you can switch to `ordered_fields`, for example because +* you can migrate any existing data already stored +* don't have any data stored +* aren't using `repr(C)` with enums (the only difference in layout between `repr(ordered_fields)` and current `repr(C)`). + +The plan + +* `cargo fix` in current edition - to replace all `repr(C)` with `repr(C#editionCurr)` +* replace all `C#editionCurr` with `ordered_fields` +* update any enums with an equivalent discriminant + * I expect there to be few cases of this, since you can already use `repr(u*)` and `repr(i*)` to get stable layouts for enums +* update to the new edition (this can be done at anytime after step 1) + +If you cannot switch to `ordered_fields` + +* `cargo fix` in current edition - to replace all `repr(C)` with `repr(C#editionCurr)` +* update to the new edition, and just keep using `repr(C#editionCurr)` + +This use-case is expected to be a small minority of cases. It will still work, and preserve the old behavior on new editions, but it may not look as nice. The best case scenario is to try and migrate the existing data to the new format, and start using `repr(ordered_fields)`. + +#### If you need `repr(C)`'s current layout and calling convention + +* `cargo fix` in current edition - to replace all `repr(C)` with `repr(C#editionCurr)` +* update to the new edition (this can be done at anytime after step 1) + +This use-case is expected to be a small minority of cases. It will still work, and preserve the old behavior on new editions, but it may +not look as nice. + +This use case cannot be catered to without expanding the scope of this RFC considerably, so it is out of scope. This plan will still allow migration to the new edition, but it may not look as nice. + +#### If you have a mixture of use-cases + +* `cargo fix` in current edition - to replace all `repr(C)` with `repr(C#editionCurr)` +* for each `C#editionCurr`, choose which guarantee you need and switch to `repr(C#editionNext)` or `repr(ordered_fields)` +* update to the next edition +* `cargo fix` - to replace all `repr(C#editionNext)` with `repr(C)` + +This use-case has to take the brunt of the work, but this is unavoidable in any proposal to fix `repr(C)`. This plan allows gradually migrating all cases to their intended `repr`, and makes it easy to track progress. + # Drawbacks [drawbacks]: #drawbacks From d44a44af4a0438ea802e70258e8fb17df869baec Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 17 Apr 2026 09:41:04 -0500 Subject: [PATCH 59/61] minor edits --- text/3845-repr-ordered-fields.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 30ffd5ce7bb..7c333953fd0 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -68,7 +68,7 @@ This RFC will require a large migration across the ecosystem. There are two majo * FFI-only crates, like `*-sys` crates or crates that expose a `C` interface * these crates should ideally have to do no work. They want the fixed `repr(C)` * Pure Rust crates that don't rely on the C calling convention - * This is for role 2, they typically don't want their layout changing from under them. So switching to `repr(ordered_fields)` will be the correct fix, with one exception: `enum`s with fields. This is likely a very small minority, since using `repr(C)` on an enum with fields is very niche, esp. because it's easy to get UB when using Rust enums with FFI. + * This is for role 2, they typically don't want their layout changing from under them. So switching to `repr(ordered_fields)` will be the correct fix, with one exception: `enum`s with fields. This is likely a very small minority, since using `repr(C)` on an enum with fields is very niche, esp. because it's easy to get UB when using Rust enums with FFI. Also it is already possible to get a platform independent layout for enums, using `repr(u*)` and `repr(i*)`. There are a number of other use-cases which aren't put on a pedestal like these two. Many of them are detailed near the [end](#migration-examples) of this document. @@ -176,7 +176,7 @@ This does miss some potential use cases 2. the crate wants to interact with hardware, and using `repr(C)` is the correct repr 3. the crate wants is using shared memory with another process, and using `repr(C)` is the correct repr. -Since this is an allow-by-default lint, I think this is fine. +Since this is an allow-by-default lint, it is fine to have some false-positives. The `suspicious_repr_c` lint takes precedence over `edition_2024_repr_c` (i.e. `edition_2024_repr_c` shouldn't be emitted if `suspicious_repr_c` is emitted to reduce noise). @@ -491,7 +491,7 @@ The plan * `cargo fix` in current edition - to replace all `repr(C)` with `repr(C#editionCurr)` * replace all `C#editionCurr` with `ordered_fields` * update any enums with an equivalent discriminant - * I expect there to be few cases of this, since you can already use `repr(u*)` and `repr(i*)` to get stable layouts for enums + * enums with `repr(C)` are expected to be uncommon * update to the new edition (this can be done at anytime after step 1) If you cannot switch to `ordered_fields` From 65bff72e0c69083372037df17a56e591e1b138a4 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 17 Apr 2026 18:41:58 -0500 Subject: [PATCH 60/61] add a note that may be `editionNext` should actually be edition agnostic --- text/3845-repr-ordered-fields.md | 1 + 1 file changed, 1 insertion(+) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index 7c333953fd0..e2c10add72c 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -580,6 +580,7 @@ See Rationale and Alternatives as well * `repr(stable)` * something else? * The name of the new repr `repr(C#editionCurr)` and `repr(C#editionNext)` are bad (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. + * maybe instead of `repr(C#editionNext)`, it should have an edition agnostic name [zullip link](https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202026-04-15.3A.20RFC.203845.20.60repr.28ordered_fields.29.60/near/585817094) * `repr(C#edition2024)`/`repr(C#edition2027)` * `repr(e24#C)`/`repr(e27#C)` * `repr(e2024#C)`/`repr(e2027#C)` From 64dffab47e327fa7a9f35c6f646cf960db857941 Mon Sep 17 00:00:00 2001 From: RustyYato Date: Fri, 17 Apr 2026 19:15:45 -0500 Subject: [PATCH 61/61] fix typo --- text/3845-repr-ordered-fields.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text/3845-repr-ordered-fields.md b/text/3845-repr-ordered-fields.md index e2c10add72c..3da58340166 100644 --- a/text/3845-repr-ordered-fields.md +++ b/text/3845-repr-ordered-fields.md @@ -580,7 +580,7 @@ See Rationale and Alternatives as well * `repr(stable)` * something else? * The name of the new repr `repr(C#editionCurr)` and `repr(C#editionNext)` are bad (intentionally for this RFC), maybe we could pick a better name? This could be done after the RFC is accepted. - * maybe instead of `repr(C#editionNext)`, it should have an edition agnostic name [zullip link](https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202026-04-15.3A.20RFC.203845.20.60repr.28ordered_fields.29.60/near/585817094) + * maybe instead of `repr(C#editionNext)`, it should have an edition agnostic name [zulip link](https://rust-lang.zulipchat.com/#narrow/channel/410673-t-lang.2Fmeetings/topic/Design.20meeting.202026-04-15.3A.20RFC.203845.20.60repr.28ordered_fields.29.60/near/585817094) * `repr(C#edition2024)`/`repr(C#edition2027)` * `repr(e24#C)`/`repr(e27#C)` * `repr(e2024#C)`/`repr(e2027#C)`