Repository: rust
Issue: Handling of packed fields causes semver hazard: layout details from other crates affect type-checking
Labels: T-lang,A-repr-packed

This is very similar to https://github.com/rust-lang/rust/issues/78586: we have another situation where the compiler calls `layout_of` to determine whether some code should compile or how it should be compiled, but `layout_of` completely ignores visibility and crate boundaries, thus creating a semver hazard.

This issue is about packed fields. When `S` is a `packed(N)` type, and we write `&s.field`, then this will be allowed by the compiler iff the type of `field` requires alignment no more than N. This has 2 semver consequences:
- If you have a packed struct with public fields, it is a breaking change to make it "more packed" by reducing the N -- that part is probably fine, it is similar to how it is obviously a breaking change to add `repr(packed)` in the first place.
- *Increasing* the alignment of a type can be a breaking change, if that type is ever used as the type of a public field in a packed struct (including in a nested way, for cases like `&s.field.inner_field`). This might not be what we want, at least I recall @scottmcm being concerned about this.

Now, of course increasing the alignment of a type can already have visible effects downstream such as increasing the size of other types, changing the result of `align_of`, etc -- but my understanding is that generally, changing the size and alignment of a type is considered semver-compatible, at least if there is at least one private field. Right now, changing a private field in some type `T` from `u8` to `u64` can break downstream code that tries to take references to fields of type `T` inside a packed struct.

There's also an interaction with closure capturing, where if a field requires alignment more than 1 and is inside a packed struct, then it will [*not* be field-closure-captured](https://github.com/rust-lang/rust/blob/25ed43ddf3e03835e5a2f2c25463ceea17a66ae5/compiler/rustc_hir_typeck/src/upvar.rs#L1624), and we capture the field of packed type instead. This means that increasing the alignment of a type from 1 to more can change how closure captures are computed, which can change when drop is run, which... that just sounds pretty bad.

So what shall we do? Even ignoring backwards compatibility issues, what would be a better behavior here? We'd basically need a notion of "this type *publicly* has an alignment requirement of at most N", and then we could use that. I am not sure how that notion should be defined though. We could also say we always reject references to fields of packed structs, but there's a lot of code out there that takes `&packed.field` where `field: [u8; LEN]` or so, and there is no good reason to reject such code. If it was just for references we could start with something like "primitive types have public alignment but ADTs do not", and we could refine this over time to accept more and more code (such as ADTs in the same crate where also all fields have types from this crate, recursively), but (a) it is very possible that even that would break too much existing code, and (b) due to the interaction with closure field capturing, even considering *more* things publicly aligned is a potential breaking change.

For closures it really seems best to just stop field capturing when there is any packed projection and don't look at field alignment. That would be a breaking change of course, but after we did it then at least it is entirely decoupled from how aligned fields are.

See [below](https://github.com/rust-lang/rust/issues/115305#issuecomment-1696860270) for some examples.

Cc @ehuss @scottmcm