---
# System prepended metadata

title: 'Design meeting 2026-04-15: `repr(ordered_fields)`'
tags: [T-lang, minutes, design-meeting]

---

---
title: "Design meeting 2026-04-15: `repr(ordered_fields)`"
tags: ["T-lang", "design-meeting", "minutes"]
date: 2026-04-15
discussion: 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
url: https://hackmd.io/xm1TzgLYTuaM7NJuDl-GlQ
---

> [!NOTE]
> This document is due to RustyYato.

- 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
[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 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
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

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 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)

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
    * 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 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`

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.

### 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)`.

## MSVC: zero-length arrays

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).

## MSVC: `repr(align)` inside `repr(packed)`

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:

```rust
#[repr(C, align(8))]
struct I(u8);

#[repr(C, packed)]
struct O {
  // At offset 0
  f1: u8,
  // At offset 1
  f2: I,
}
```

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-cases[<sup>1</sup>](#ordered_fields_align).

## MSVC-x32: u64 alignment

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).

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):
```rust
// Size: 24
#[repr(C)]
struct Floats {
  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).

Any fix for this requires splitting up `repr(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(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 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`
  --> 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(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`.

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`.

```
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
[reference-level-explanation]: #reference-level-explanation

## `repr(C)`

> 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)`

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
>
> 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.

```rust
// assuming that u32 is aligned to 4 bytes
// size 16, align 4
#[repr(ordered_fields)]
struct FooStruct {
    a: u8,
    b: u32,
    c: u16,
    d: u32,
}
```

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

```rust
// assuming that u32 is aligned to 4 bytes
// size 4, align 4
#[repr(ordered_fields)]
union FooUnion {
    a: u8,
    b: u32,
    c: u16,
    d: u32,
}
```

`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).

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
// tag = i16
// represented as i16
#[repr(ordered_fields, i16)]
enum FooEnum {
    VarA = 1,
    VarB, // discriminant = 2
    VarC = 500,
    VarD, // discriminant = 501
}

// tag = u16
// represented as u16
#[repr(ordered_fields, u16)]
enum FooEnumUnsigned {
    VarA = 1,
    VarB, // discriminant = 2
    VarC = 500,
    VarD, // discriminant = 501
}
```

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
#[repr(ordered_fields, i8)]
enum BarEnum {
    VarFieldless,
    VarTuple(u8, u32),
    VarStruct {
        a: u16,
        b: u32,
    },
}
```

```rust
#[repr(ordered_fields)]
struct BarEnumRepr {
    tag: BarTag,
    data: BarEnumData,
}

#[repr(ordered_fields)]
union BarEnumData {
    var1: VarFieldless,
    var2: VarTuple,
    var3: VarStruct,
}

#[repr(ordered_fields, i8)]
enum BarTag {
    VarFieldless,
    VarTuple,
    VarStruct,
}

#[repr(ordered_fields)]
struct VarFieldless;

#[repr(ordered_fields)]
struct VarTuple(u8, u32);

#[repr(ordered_fields)]
struct VarStruct {
    a: u16,
    b: u32
}
```

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 the layout of the entire struct
fn get_layout_for_struct(field_layouts: &[Layout]) -> Result<(Vec<usize>, 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()))
}

fn layout_max(a: Layout, b: Layout) -> Result<Layout, LayoutError> {
    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<Layout, LayoutError> {
    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 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
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 that works. So BigInteger
    discriminants: &[BigInteger],
    variant_layouts: &[&[Layout]]
) -> Result<Layout, LayoutError> {
    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 tag_layout = get_layout_for_tag(discriminants);

    let (_, layout) = get_layout_for_struct(&[
        tag_layout,
        variant_data_layout
    ])?;

    Ok(layout)
}
```

### Migration to `repr(ordered_fields)`

The migration will be handled as follows:
* after `repr(ordered_fields)` is implemented
    * 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
        * 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.
    * The warning for previous editions will continue to be in effect

# 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

* `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)
* 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

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

* 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?
    * 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
* <a id="ordered_fields_align"></a>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)
* 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

* 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
* https://internals.rust-lang.org/t/consistent-ordering-of-struct-fileds-across-all-layout-compatible-generics/23247

---

# Discussion

## Attendance

- People: TC, Josh Triplett, Tyler Mandry, Niko Matsakis, Jack Huey, Zachary Sample, Yoshua Wuyts, Ralf Jung, RustyYato, Frank Steffahn, Nurzhan Saken

## Meeting roles

- Driver: TC
- Minutes: Nurzhan Saken

## Vibe check

### nikomatsakis

Ship it. Or maybe, hmm, I want to hear what Josh thinks first. =)

Niko: Transition is the hard part.

### tmandry

In favor of splitting the role between repr(C) and repr(ordered_fields). The RFC seems great. Rust should have perfect C interop and also allow controlling layout.

### scottmcm

(Not present.)

### Josh

Vibe on the concept itself: yes please, we've needed this for a long time! We've conflated two different things in `repr(C)`, and this will separate them.

Vibe on the migration plan: *ow*, this is going to hurt.

> * This will cause a large amount of churn in the Rust ecosystem

No kidding.

I think we should socialize this, heavily, but I think it's going to be incredibly hard to warn about this without giving many people a warning they just have to suppress. I think the right answer for *many* uses of `repr(C)` in the ecosystem will be "allow the warning, wait for the new edition to fix bugs in `repr(C)`", *not* "switch to `repr(ordered_fields)`". Like some other edition migration lints, this is in the category where the compiler can migrate to a zero-change compatible thing, but what *many* people will *actually want* is closer to "change your edition in `Cargo.toml` and compile, don't use the migration". That doesn't seem ideal.

At the very least, I think even if it's an edition migration, it *shouldn't* be an FCW in any form. Then, in that case, fine, it's an automated migration for people who want fully automated compatible migrations, but can we do something to communicate to people "you probably don't actually want this!"?

We should also be explicit that going forward, we reserve the right to fix further discovered bugs in `repr(C)` without a further migration, in order to match the platform ABI. That won't be a problem to the extent it's being used for C interoperability, and "stable layout between Rust-only code" should be using the new `repr(ordered_fields)` instead.

> A warn-by-default lint when `repr(C)` is used, and there are no `extern` blocks or functions in the crate (on all editions)

I think this is too strict. There are legitimate reasons to define a `repr(C)` structure without having extern blocks or functions; consider something that's the ABI of a shared-memory operation, or a piece of hardware, which has no `extern "C"` functions. (The proposal acknowledges this, but I'm treating this alongside my mention above that I think these warnings *in general* will be much too pervasive and generally steering in the wrong direction.)

How stable is this over time? If the answer is "permanently", or close enough to "permanently" that we are willing to version it, should this be `repr(stable)`? 🤞

> * Should this `repr` be versioned?

We don't strictly *need to version it explicitly until we have a second version. But we could, if we want to more clearly advertise that it's stable. In which case, I'd love to see us call it `stable_v1` or similar.

---

Tyler: On migration, the RFC is saying there will be a lint people will have to suppress. It would be up to the user whether to change the layout. Does that sound right?

Josh: If we can advertise that appropriately, there shouldn't be a problem.

(move to migration item below)

### TC

I'm happy to see work in this direction; thanks to RustyYato for that and to RalfJ for helping this along.  We've been pushing, across the language, toward a design axiom of "crisp behavior".  This does that.  Migration is obviously the hard part.  I have some questions about that and about the handling of enums.  I need to think more about some of the items that were raised in discussion on the PR.

## Checking in with the author

TC: RustyYato, what would be the valuable things for us to discuss to help you move this forward?

RustyYato: This is about what I was expecting. I knew migration would be contentious because of how noisy it is. We can talk about migration later in the session. Unresolved questions, in order of importance: 1) migration plan, 2) do we want a specified ABI for this? This made other proposals large and hard to finish. I'd like to get to a minimal finished thing before attempting something more ambitious.

Josh: As someone who was driving crabi, you're right to have this concern. Ralf's lint suggestion below would be worth evaluating for reducing lint noise.

TC: What's the reason for not requiring the type of the tag to be specified with `repr(ordered_fields)`?

RustyYato: repr(C) currently depends on ??(c_int?). I'd like to be able to migrate off of repr(C) without changing behavior.

TC: So it's a migration issue.

TC: What about shipping `repr(ordered_fields)` separately from doing the migration.  Is there value in that?

RustyYato: We could ship it, but... The migration question is unavoidable.

Ralf: Not aware of other issues than migration. Seems unavoidable, and if we wait longer, it will get more painful. I like the heuristic based on extern blocks. I don't write that much code that does this sort of thing.

## Typo note (no in-meeting discussion needed)

Josh:

> Introduce two new warnings

This suggests three new warnings. :)

RY: Will update this in RFC :)

## Migration discussion

(As raised by Josh and Tyler above in vibe checks.)

TC: What's the first part of the migration question we should tackle?

RY: I'd like to go over the timeline and what lints we'd want, where we'd fire them.

Tyler: In the world where we've done the migration, are we going to change the meaning of repr C in all editions?

RY: Good question. I had an unresolved question for that. Not sure how we'd handle existing editions. Maybe in the future we could switch, but I think we can kick that can down the road a bit. For now we can leave repr C as is.

Tyler: That implies the existence of 3 reprs (repr C-legacy, C-new, ordered)

Ralf: C reprs are an option (having 3 reprs). They resolve the asymmetry by making everybody change everything. The RFC as written doesn't propose 3 reprs. Says you need to go to the next edition for the new repr. Don't change the meaning of existing code, also... if you stay on repr C and switch the edition, you get repr(ordered_fields) (?). We could add ?, and that would lower the migration churn.

Frank: The previous point about "having 3 reprs" sounds like it's more about having "3 *names* for the reprs", assuming that the description is accurate that old meaning or `repr(C)` should match the general meaning of `repr(ordered_fields)` (the latter on all editions).
(Also related to the point brought up 2 points below “repr(system”).) How about a way to tag the edition number on the repr? Like: `repr(e2024#C)`. This style of syntax could also come up on edition-dependent name resolution.  Another benefit is replacing the edition automatically, so `repr(C)` migrates automatically to `repr(e2024#C)`. This way you have a clear marker of "this code was automatically migrated" but you might mean to change it to *new* `repr(C)` or make explicit the choice that this should indeed be `repr(ordered_fields)`.

TC: Wearing a bit of a Reference hat: On the other side of this migration, how well are we then able to specify what `repr(C)` means, exactly, in terms of semantics? And is there ever a reason that we'd need to say `repr(C26)` or is that never going to matter?

Ralf: 1) Frank, I'd prefer not to tangle this up with ...

Frank: Didn't mean to tangle it up, just introduce a syntax that *could* be extended...

Ralf: With your proposal, we'd have 4 reprs. TC, seeing a ?? API break is unlikely. If a target does that, it becomes a new target triple. The C standard doesn't say what the layout is. Most vendors don't change this. I don't think we'd need to have that dimension.

Niko: Frank's proposal reminds me of macro nonterminals.

Frank: I was thinking of that too.

Tyler: I like that. Not sure we need 4 repr names. 

Niko: Usually you want some intersection so that it's more expressible in every edition.

Frank: You could change the names, then migrate automatically, or vice versa.

Tyler: Makes sense. You'd expect ?? to go back to using C or ordered_fields.

Ralf: This proposal would mean that all repr C on older editions mean ordered_fields, and on newer means C ABI. The migration lint would change C to C27 and C24 to C when you update the edition. Right?

Tyler: Are you claiming that repr C today is ordered_fields?

Ralf: That's documented, and people want this because this is almost always what C compiler does.

Frank: Having several names is hard for discussion.

Ralf: The only repr that would change meaning is C.

Niko: I expected C24 to mean what C means today, broken as it is. ?? And C would change depending on edition.

Ralf: I don't think they're different. It's an odd choice to use ?? because it's target-dependent.

Niko: Whether you use C24 or ordered_fields is a matter of intent.

Ralf: Yeah. You'd write repr(ordered_fields) to indicate that you chose so and did the work to migrate.

Josh: Love this idea on the basis on being able to distinguish whether you migrated deliberately or relied on automatic migration. I like the idea of naming it C??edition or rust ...

Ralf: If you use C27 already in an older edition to indicate that you did the check, the time to apply the migration is after ... ordered_fields. Usually you'd enable edition warnings and...

RustyYato:

RY: In edition 2024 youd apply C27 and then do an edition upgrade one you're able to. In edition 2027 you still have ?? because edition fixes happen...

Niko: That's called an idiom lint, not unusual.

Jack: Would be nice to have a table of different behaviors by repr, the expected names and editions. I could imagine in Ed2024 the behavior has one name, and in another edition it's different.

Frank: Would make sense to call ?? because it's the first edition where it...

tmandry: What should we be consistent with?

Niko: Fragment specifier...

TC: When we change the meaning of a macro specifier, and we need to do a migration, we add a new macro fragment specifier.  This is RFC 3531.

Ralf: I put the table at the bottom of the document. Feel free to update if I got something wrong.

TC: Are there design compromises in the design of `repr(ordered_fields)` due to wanting to use it as a drop-in replcement for migration? If we did the migration differently, by having an edition 2024 C that's available on newer editions, are there other choices we would want to make?

RY: The only tradeoff I can think of is dealing with enums. I think it's fine to keep it as is. (...) There isn't a good reason to duplicate that (`repr(u8)`).

TC: We didn't follow the platforms into any weird layout semantics?

RY: No, there's none of that.

Ralf: We may have for the ABI in some cases, but not layout.  We wouldn't want to specify those bits.

Tyler: The table looks straightforward to me.

RY: Anything else on the migration plan?

Jack: Is this an actual plan or just discussing to come up with one? I think the discussion was thinking on different names.. do we need both ordered_fields and c24?

Ralf: Technically, we don't need it because it's the same as c24, but... it's an awkward name to use. It's a temporary name to aid migration.

Tyler: Agreed. We need the explicit names, but the actual names (c24, c27) need to be changed probably. Sounds like they talk about the version of C instead of Rust.

Jack: Could imagine that we don't need c2024. I could imagine c2027 if you're in 2024 edition. You could be explicit and say c24/ordered_fields, but... I don't know that discussing this here makes much sense compared to making an actual plan...

Frank: To counter the "don't need c2024", that one was meant to be used as the *result of automatic migration* (also compare "Manual migration work" under the table below), and it also serves to address the point Josh had made about the automatic migration being technically correct but often not *what you want*-correct; so it serves as a marker for "this has been automatically migrated".

Niko: I like the table, the names and I get the concept. It would be nice to see in the RFC explicit migration workflows people would do. I can imagine there are people that prefer to do that in an older edition. The motivation is to ensure that people can migrate on their own schedule, not ours.

TC: The exact rule ?? was, ... I don't want to have to pin the edition. I prefer a semantically meaningful name (below).

TC: In RFC 3531, we had said that if a semantically-meaning name could be chosen for the new specifier (representing the old behavior), we would use that.  Only if we couldn't find one, we'd append the current edition to the name.  In that spirit, how about `repr(platform_C)` for the name of "no, I really meant `repr(C)`" in older editions?

Tyler: Not sure is we want to reuse extern keyword. I like platform. I think c24 has a function which Frank explained. I could see a migration path where because C has a ordered_fields-like beahvior, we could migrate to that, but...

Frank: Reiterating my idea that this would be beneficial for general `e2024#` mechanisms because you can learn *once* that `e20XX#ident` means exactly the same as "the meaning of`ident` on edition `20XX`. (This would replace the name `C2024` with `e2024#C` and `C2027` with `e2027#C`)

Niko: I don't know what people mean when they say platform C. Contrary to what you were saying, regarding c24, I don't think there's a semantic grouping for it because it's a broken approximation of what C did. 

TC: You're right.  Something more akin to `repr(broken_C)` would be more in the spirit.  `repr(platform_C)` would be better for making the fixed version available in older editions.

Josh: Yes, and putting the name directly into it makes a lot of sense.

Ralf: ordered_fields is the clear semantic name, but it's not what we want to use here because we also want to help people migrate.

Niko: We want people to 'tell us what they want', whether that's C or another repr.

Josh: "Here's a name for the thing you can migrate to..." The name for the old thing can be ugly, but it shouldn't matter because you're migrating off of it.

TC: Taking a step back, regardless of the name, what do we want people in old editions to do exactly? Do we want to lint them towards `ordered_fields`, toward the unbroken C behavior, etc.?

RY: Those are the options. We probably need heuristics.

TC: But we want to make both available on old editions, right?

RY: Yeah.

Niko: Proposal:

* C2024
* System-C or Platform-C
* ordered_fields
* "C" changes from C2024 to "Platform-C"

Niko: TC, "we would encourage you to either migrate to C2027 or ordered_fields; you could turn on that lint in the older edition... and we'll move you to the right name in the next edition..." Two flows we expect: 1) you do it in the old edition and .. C2027, 2) or the new edition, where you go to ?? or ordered_fields.

TC: The reason we'd want to have a `C2024` would be that we want a way to migrate people into the new edition while leaving an indication they haven't made a decision yet.

Niko: That's the reason.

TC: Makes sense.

Jack: I want to go further and say you cannot use C2024 in a newer edition. Similarly, I want C2027 to be deny by default, and an edition lint would put it into allow.

TC: That doesn't work as stated because we ned to move people into the intersection.

Jack: So in 2024 there's a lint, if you're using C, you need to move to C2027 or ordered_fields. The migration would replace...

Frank: Cannot be an error, only a deny by default lint.

Jack: The migration fix will change C to C2024, and will change the edition simultaneously

Niko: This is not what happens.  We let people update the edition separately from doing the migration so that they can do it partially.

Ralf: Seems weird because it inserts an allow that you later have to remove...

Tyler: I'd do warn-by-default, not deny...

Josh: I think we're doing design rather than handing a set of constraints to people doing the design.

Tyler: One of the guide posts should be, you can write a macro and have its meaning not changed depending on edition

Tyler: There should be a stable name that works across all editions with the behavior.

Josh: I see. I agree that macros should have a way of generating something that works. But we already have macros where ?? itself is affected by the edition... 

Tyler: Still would be a good property to have.

Josh: I'm suggsting the codegen to care about the edition it's generating for. Sometimes the code will depend on the edition. repr C shouldn't have to have a more complicated name because what it means changed in the past. It should still be called repr C.

TC: We're at time.  RustyYato, how did we do?  Has this been helpful?  What are the next steps that you see?

RY: Overall, this has been good. Lots of migration-centered discussion, which is great. I'll expand on it in the RFC based on this discussion.  What do we do after that?

TC: If you have more deep questions for us to discuss, you'd put together a design document for another meeting talking about what you took away from the last one and what the other questions we should help to answer are.  And, otherwise, if you think that after the updates it's ready to go, simply nominate it for us to have a look so we can propose it for FCP merge.

Josh: Thanks for working on this, as it's been delayed for a long time.

Tyler: +1 to Josh. Thanks for pushing this forward. Excited to ?? the RFC when it's ready to go.

RY: Should take a few weeks, probably.

TC: Thanks to RustyYato for putting this together and for being with us here today; thanks to everyone else for joining us and helping us to work through this.  See everyone next Wednesday.

## Migration plan table (proposal of how "4 names" can work)

*(exact syntax of `C2024` and `C2027` yet to be determined)*

| repr name        | edition <= 2024 | next edition   |
| ---------------- | --------------- | -------------- |
| `ordered_fields` | ordered fields  | ordered fields |
| `C`              | ordered fields  | C system ABI   |
| `C2024`          | ordered fields  | ordered fields |
| `C2027`          | C system ABI    | C system ABI   |

Next edition migration lint: change `C` to `C2024`
Next edition idiom (post-migration) lint: change `C2027` to `C`

The difference between ordered_fields and C2024 is intent: C2024 means "I haven't yet figured out which one I need" (aka "this was inserted by auto-migration"), ordered_fields means "I definitely want the ordered fields layout".

Manual migration work:
* you can work in 2024 edition, and change `C` into either `C2027` *\[to **change** behavior to "C system ABI"]* or `ordered_fields` *\[to express intent of being non-"C system"]*
* you can work in 2027 edition, and change (result of auto-migration) `C2024` into `C`  *\[to **change** behavior to "C system ABI"]* or `ordered_fields` *\[to express intent of being non-"C system"]*

## Lint details

RalfJung: Could we make the lint just fire when repr(C) and repr(ordered_fields) disagree on any known target? Or some variant of that?
The downside of course is that we might discover more discrepancies in the future...

Josh: To confirm, you mean warn on a structure whose layout would differ? (And, warn unconditionally no matter what target we're compiling for, not just when compiling for *that* target?) If so, that sounds like a potentially reasonable plan to reduce the amount of noise and lint-suppression in the ecosystem.

Ralf: Yes. But that could be hard to implement... even impossible, with generics. (Or it has to be very conservative then.)

## `repr(system)`

Zachary Sample: (covered two above in the migration section) I vaguely recall discussion of `repr(system)`. Perhaps this (or another bikeshedded name) could be introduced to opt-in to the next-edition `repr(C)` behavior on existing editions. Or maybe like `repr(C(2027))`

Ralf: Let's avod scope creep... this is one of the things that bogged down https://github.com/rust-lang/rfcs/pull/3718

## Second lint (`suspicious_repr_c`)

RalfJung: There's suddenly a second lint (`suspicious_repr_c`) in the Guide-level explanation but it's not explain when it fires. I am very confused about this entire paragraph.

## Tooling to encourage migration

nikomatsakis: In Rust-long-ago, all struct fields were ordered. Then we started sorting them by size. This broke a lot of unsafe code. We shipped an environment flag that let you supply a certain amount of "reordering fuel" -- set to 0, it would disable reordering altogether. Set to infinity, it was today's behavior. Setting it to values in between let you binary search to find *which specific struct* was being reordered and causing your issue.

I wonder if we could do something similar, e.g., by "simulating" changes to `repr(C)`? Not sure what that would mean, but I'm wondering about ways that we can help sort out the "you really wanted C" from "you just wanted ordering". I suppose looking for structs that actually get passed to `extern "C"` functions might also be a thing we could do, eh?

## repr(ordered_fields) enum without explicit discriminant type

RalfJung: Why is this a warning and not an error?

Answer: To ease migration.

## get_layout_for_enum spec

RalfJung: I canot make sense of this. `variant_data_layout` is a list of layouts but then later used as-if it was just a single layout...?
The code seems ill-typed.

Zachary Sample: (line 362) `variant_layouts` is the list of layouts of the variants; `variant_data_layout` is a single `Layout` produced by `try_fold`ing over `variant_layouts`. The code compiles if you stub out `BigInteger`: https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=fbb5253d1137023f53b4e362e3cc8961

## `repr(ordered_fields, packed(N))` and aligned fields

tmandry: I think we should pick a behavior and allow it, possibly with a lint. I don't have a strong preference on which behavior we pick. If we don't respect the align of the type, we should prevent taking direct pointers or references to it.

Ralf: Yeah, we already allow this anyway using generics.  I think it should behave the way it already does: a field with align(N) is treated like any other field with that alignment, i.e., types only have "one kind of alignment" that align(N) overwrites that. packed(M) then caps that at M. This is the GCC behavior, not the MSVC behavior.

## Bikeshed

tmandry: I think `ordered_fields` is a bit long, especially since we are encouraging migration from the one-character `C`. Personally I favor `ordered`.

tmandry: Another point: C and Rust are capitalized but packed is not. I guess the convention is proper nouns are capitalized?

## Questioning assumptions about Rust layout guarantees

tmandry: For the layout-consistency-between-two-structs use case, "why can't we just" make a guarantee that structs with the same field layouts listed in the same order always have the same layout? I realize that `-Zrandomize-layout` (sp?) exists and is useful, but we could still implement this guarantee even with that flag.

tmandry: I'm also wondering about a stable version of the sort-by-alignment algorithm we do today.

Zachary Sample: For the layout-consistency-between-two-structs use case, I had an idea a while ago about `repr(deterministic)` whose layout is unspecified, but guaranteed to be the same for two types with the same field size/alignments "This repr allows the compiler to make some layout optimizations, but requires that the optimizations it makes are deterministic within a compilation based only on the size, alignment, and declaration order of fields and variants, any other repr annotations on the type." https://github.com/zachs18/rust-rfcs/blob/deterministic-repr/text/0000-deterministic-repr.md ("within a compilation" is probably not restrictive enough, but like, what I meant was two compiler invocations with "the same enough" flags give the same layout)
