Introduce a few new reprs (all names are placeholders and can be bikeshedded if this RFC is accepted):
repr(ordered_fields)repr(C#editionCurr)repr(C#editionNext)
The meaning of repr(C) will change from C#editionCurr in the current edition to C#editionNext 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): 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): 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): On current editions the meaning will not change. On future editions this will be defined as the same representation as the platform C compiler, as specified by the target-triple. In future editions, this repr should only be used for FFI.
Introduce a few new warnings and errors
- A error when
repr(ordered_fields)is used on enums without the tag type specified. - An edition migration warning, when updating to the next edition, that the meaning of
repr(C)is changing. This will have an automated fix torepr(C#editionCurr) - A warn-by-default lint when
repr(C)is used, and there are noexternblocks or functions in the crate (on all editions) - An idiom lint when
repr(C#editionNext)is used on the next edition, with an automated fix torepr(C). - 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 betweenrepr(C)orrepr(ordered_fields)
Motivation
Currently repr(C) serves two roles:
- Provide a consistent, cross-platform, predictable layout for a given type
- Match the target C compiler's struct/union layout algorithm and ABI
But in some cases, these two roles are in tension due to platform's C layout not matching the simple, general algorithm Rust always uses:
- On MSVC, a struct with a single field that is a zero-sized array has non-zero size. (https://github.com/rust-lang/rust/issues/81996)
- On MSVC, a
repr(C, packed)withrepr(align)fields has special behavior: the innerrepr(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 forbiddingrepr(align)fields inrepr(packed)structs, but that check is easily bypassed with generics.) - On MSVC-x32,
u64fields 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,
f64fields are sometimes, but not always, get 8-aligned in structs. (https://github.com/rust-lang/rust/issues/151910)
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. This puts us in a similar position to std::env::set_var, where Rust claimed that it was thread-safe, the POSIX standard said it's not, and Rust had to do a breaking change to fix this.
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 a new repr and some sort of transition plan to move people to the new repr.
This code generally falls into one of these buckets:
- rely on the exact layout being consistent across platforms/versions
- for example, zero-copy deserialization (see rkyv)
- manually calculating the offsets of fields
- This is common in code written before the stabilization of
offset_of(currently only stabilized forstruct)/&raw const/&raw mut - This is still required if you are manually doing type-erasure and handling DSTs (for example, implementing
erasable::Erasable)
- This is common in code written before the stabilization of
- manually calculating the layout for a DST, to prepare an allocation (see slice-dst, specifically here)
- match layouts of two different types (or even, two different monomorphizations of the same generic type)
- see here, where in
allocthis is done forRcandArcto give a consistent layout for allT. For example, by ensuring that the strong count is at offset0, and the weak count at offsetsize_of::<usize>().
- see here, where in
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 for anyone upgrading to the next edition. There are two major use-cases that are prioritized in this document.
- FFI-only crates, like
*-syscrates or crates that expose aCinterface- These crates should ideally have to do no work. They want the fixed
repr(C)
- These crates should ideally have to do no work. They want the fixed
- 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:enums with fields. This is likely a very small minority, since usingrepr(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, usingrepr(u*)andrepr(i*).
- This is for role 2, they typically don't want their layout changing from under them. So switching to
There are a number of other use-cases which aren't as highly prioritized like these two. Many of them are detailed near the end 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).
This is justified because it is expected that these two major cases will cover the vast majority of cases seen in the wild.
Layout issues
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).
Since this RFC is trying to be the minimal fix to split repr(C), any fix will either depend on this RFC or contain this RFC as part of the fix. Accepting this RFC will provide an incremental upgrade to the situation.
MSVC: zero-length arrays
On Windows MSVC, repr(C) doesn't always match what MSVC does for ZST structs (see this issue for more details)
// should have size 8, but has size 0
;
Of course, making SomeFFI size 8 doesn't work for anyone using repr(C) for role 1. They want it to be size 0 (as it currently is).
MSVC: repr(align) inside repr(packed)
This also plays a role in #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:
;
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.
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-cases1.
MSVC-x32: u64 alignment
Splitting repr(C) also allows making progress on dealing with the MSVC "quirk" rust-lang/rust#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.
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.
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.
AIX: f64 alignment
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):
// Size: 24
There is no way to obtain such a layout using Rust's repr(C) layout algorithm. Currently, cis at offset 16, and the size is 24 bytes. If we simply lower the alignment of f64 to 4, we'll get the right offsets, but the size will be 20 bytes. So either way, we'll need to adjust the rules for repr(C) to get a proper fix.
For more details, see this discussion on irlo.
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(C#editionCurr) is the same as repr(C) on current editions. It lays out
structfields in order, while ensuring that they are well alignedunionfields at offset 0- field-less
enums as an implementation defined integer - general
enums as a struct of a tag and a union. Where each field of the union is a struct containing each field of the variants. (NOTE: this is how they are handled inrepr(C)in current editions)
repr(C#editionNext) will be defined as the same as what the C compiler of the given target would do (both in terms of layout and calling convention).
repr(C) has an edition specific definition
- on current editions, it matches
repr(C#editionCurr) - on future editions, it matches
repr(C#editionNext)
Using repr(C) in all current editions 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 torepr(C#editionNext)or upgrade to the new edition - If you are not using
repr(C)for FFI, then you should switch torepr(ordered_fields)
The machine-applicable fix is provided to allow users to do migrations on their own terms. This way a user can do cargo fix to get warning free code. Then choose one of the following
- fix uses of
repr(C#editionCurr)at their leisure, then update to new edition - update to new edition, then fix uses of
repr(C#editionCurr)at their leisure
Here's an example of how the lint could look
warning: use of `repr(C)` in type `Foo`
--> src/main.rs:14:10
|
14 | #[repr(C)]
| ^^^^^^^ 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(C#editionNext)` or `repr(ordered_fields)`
Using repr(C)/repr(C#editionCurr)/repr(C#editionNext) on all editions (including in future editions) 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 to reduce noise, since the edition lint should do the heavy lifting. This is still provided as a tool for interested users to reduce their reliance on repr(C) (or its variants) when it is probably not needed. The only improvement that repr(C) provides over repr(ordered_fields) is by having a stable calling convention. So when that's taking out of the picture it is usually better to select repr(ordered_fields), which provides better cross-platform guarantees than repr(C).
If any extern block or function (including extern "Rust") uses the given type 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 some potential use cases
- where a crate provides a suite of FFI-capable types, but does not actually provide any
externfunctions or blocks. - the crate wants to interact with hardware, and using
repr(C)is the correct repr - 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, 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).
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.
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)orrepr(ordered_fields). - On future current editions it will guide people towards
repr(C)orrepr(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 unrelated to FFI.
Reference-level explanation
repr(C#editionCurr)
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. 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#editionNextrepresentation 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 for which the
C#editionNextrepresentation is an error.
- edited version of the reference 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). 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 conform with the target C compiler.
Flexible Array Members
Given that all major C compilers (at the time of writing) put T field[N]; and T field[]; at the same offset. repr(C#editionNext) will make a guarantee for the last field of a repr(C#editionNext) struct.
- If the only difference between two
repr(C#editionNext)structs is the type of their last field - If the fields' types are
[T]or[T; N] - Then the exact offset will also be the same as the equivalent
Ctype that has the field type as a flexible array member, i.e.T field[]; - Then the exact offset will also be the same as the equivalent
Ctype that has the field type as an array member, i.e.T field[N];(of anyN) - Then the offset of that field only depends on
T(and cannot depend on the length of the array/slice)
If a generic repr(C#editionNext) type has an generic unsized final field (see Coercable<T> below), then the obvious unsizing coercion from Coercable<[T; N]> to Coercable<[T]> is allowed. i.e. Coercable<[T; N]>: Unsize<Coercable<[T]>>.
This means that the following four types have data_array and data_slice at the same offset.
// in Rust
// in C
;
;
If there exists a target where the C compiler doesn't put T field[N]; and T field[]; at the same offset, then these rules may be revised on those targets. Currently, allow known C compiler put array members and flexible array members at the same offset.
Trait objects
If field of a repr(C#editionNext) has the type of a trait object, then the layout of the type is unspecified. If this came from a generic instantiation, then coercions form a concrete type to the trait object are disabled. For example
- you could not derive
Coercable<i32>: Unsize<Coercable<dyn Debug>> - you could not cast from
*const Coercable<i32>to*const Coercable<dyn Debug> - the layout of
Coercable<dyn Debug>is unspecified
If the type of a field is always a trait object, then a hard error is reported.
Note: the layout is unspecified to avoid adding a new post-mono error. By making the coercion impossible, it becomes impossible to safely construct a valid pointer to a *const Coercable<dyn Debug>. When using feature(ptr_metadata), it may be possible to construct a pointer to *const Coercable<dyn Debug> using core::ptr::from_raw_parts, but it is not safe to use.
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 current editions this means
repr(C#editionCurr) - on future editions, this means
repr(C#editionNext)
repr(ordered_fields)
Note: This provides a nice name for a Rust-specific, stable, consistent layout.
The
ordered_fieldsrepresentation 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 typeThis representation can be applied to structs, unions, and enums.
- edited version of the reference 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.
// assuming that u32 is aligned to 4 bytes
// size 16, align 4
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
- all fields are at offset 0
// assuming that u32 is aligned to 4 bytes
// size 4, align 4
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 tag type must be specified. i.e. repr(ordered_fields, u8) or repr(ordered_fields, i32). It is a hard error to leave the tag type unspecified. The error should suggest the smallest integer type that can hold the discriminant values.
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.
// tag = i16
// represented as i16
// tag = u16
// represented as u16
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! This may be surprising, but it matches precedent -- adding repr(C) to repr(iN)/repr(uN) has a similar effect.
For example, this would be laid out the same as the union below
union BarEnumData
;
;
In Rust, the algorithm for calculating the layout is defined precisely as follows:
/// Takes in the layout of each field (in declaration order)
/// and returns the offsets of each field, and the layout of the entire struct
/// 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
/// 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 are left as an exercise for the readers
/// NOTE: the enum tag is always at offset 0
packed
When repr(ordered_fields, packed(N)) is applied to a struct, any field with a type of alignment > N has its alignment capped to N. This applies even if that type has a repr(align(M)) attribute applied. Otherwise, the rules are the same as described above.
For example, for a struct
// size = 64, align = 64
;
// size = 64, align = 4
// size = 64, align = 1
This behavior is chosen since it is consistent with the handling of naturally over-aligned fields (such as if the field has type u64), and since pre-mono errors don't work in the context of generic types. So a behavior has to be chosen for the following type, and the only consistent behavior is the one described above:
Migration Plan
The migration will be handled as follows:
- after the reprs outlined in this RFC are implemented
- at this point
repr(C)andrepr(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
- 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
- add the
suspicious_repr_clint to help people migrate away fromrepr(C).
- at this point
- Once the next edition rolls around (2027?)
- at this point all of
repr(C)andrepr(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)andrepr(C#editionNext)
- at this point all of
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 betweenrepr(ordered_fields)and currentrepr(C)).
The plan
cargo fixin current edition - to replace allrepr(C)withrepr(C#editionCurr)- replace all
C#editionCurrwithordered_fields - update any enums with an equivalent discriminant
- enums with
repr(C)are expected to be uncommon
- enums with
- update to the new edition (this can be done at anytime after step 1)
If you cannot switch to ordered_fields
cargo fixin current edition - to replace allrepr(C)withrepr(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 fixin current edition - to replace allrepr(C)withrepr(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 fixin current edition - to replace allrepr(C)withrepr(C#editionCurr)- for each
C#editionCurr, choose which guarantee you need and switch torepr(C#editionNext)orrepr(ordered_fields) - update to the next edition
cargo fix- to replace allrepr(C#editionNext)withrepr(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
- 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
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
- 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
Prior art
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
- 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 switchaccepting this RFC will enforce this guaranteerepr(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,
Should thisreprbe 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 a generic
repr(C#editionNext)struct instantiated with a trait object as it's trailing field be a post-mono error? - 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 ofNot in this RFCrepr(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 correspondingCcompiler (like fieldless structs in MSVC)?- discussion: https://github.com/rust-lang/rfcs/pull/3845#discussion_r2319138105
Shouldyesrepr(ordered_fields, packed(N))allowalign(M)types whereM > 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 au32in arepr(packed(2))type)
Should unions expose some niches?no- 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 onThis is now a hard errorrepr(ordered_fields)applied to enums when explicit tag type is missing (i.e. norepr(u8)/repr(i32))- Since it's likely they didn't want the same tag type as
C, and wanted the smallest possible tag type
- Since it's likely they didn't want the same tag type as
- 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.repr(linear)repr(ordered)repr(sequential)repr(serial)repr(consistent)repr(declaration_order)repr(stable)- something else?
- The name of the new repr
repr(C#editionCurr)andrepr(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 zulip link repr(C#edition2024)/repr(C#edition2027)repr(e24#C)/repr(e27#C)repr(e2024#C)/repr(e2027#C)- likely too confusing to actual C standardsrepr(C24)/repr(C27)- something else?
- maybe instead of
Future possibilities
- Add more reprs for each target C compiler, for example
repr(C_gcc)orrepr(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
- https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247