Consistent ordering of struct fileds across all layout-compatible generics

Currently, Rust has two main ways of ordering the struct fields: repr(Rust) and repr(C). These two ways are the two extremes: repr(Rust) basically doesn't guarantee anything, while repr(C) forces the fields to be ordered exactly as declared.

However, these extremes clash with generics and sound type casting. Let's say you have struct Foo<A> { foo: u32, bar: A } and for some reason you need to do some kind of conversion between Foo<T> and Foo<U> where T and U have compatible layout (in that direction). If the struct is repr(Rust) then you cannot do such conversion by transmuting because it'd be unsound. You can write safe code, which works but adds overhead in case the fields actually do end up being in different order. OTOH, there repr(C) which allows the transmute but wastes space in some cases because no reordering is allowed.

What's needed is a third repr, let's call it repr(consistent) here: it guarantees that as long as the individual generic fields are layout-compatible the transmute is sound but no other guarantees are given. So the fields can still be reordered if Foo<A> and Foo<B> would not be allowed to be transmuted anyway. Note that repr(transparent) does this but only for one-field structs but this is needed for multiple-field structs.

I tried to find anything on this topic but couldn't, did nobody need this?

Notably, one obvious limitation is what to do about conversions between say u32 and [u8; 4]. They have different alignment, so struct reordering would be useful, but same size so casting would be also useful. Maybe having two reprs to cover both use cases would make sense.

I think it is possible to implement, but they don't like including anything unless you give them enough motivation. From reading the post, motivation is "sometime in some circumstances when that and that in that case you need to transmute or it will be not absolutely optimal". Like, reordering of the fields will be roughly the same speed as moving. And how much do you want to do that? Is it worth it?

1 Like

Well, you'd still ending up wasting space in some cases because the compiler has to reason in generality and not particular pairs of T and U. E.g. if it knew that both have particular niches then it might choose a different placement for the generic slot.

Notably, one obvious limitation is what to do about conversions between say u32 and [u8; 4]. They have different alignment, so struct reordering would be useful, but same size so casting would be also useful.

The layout algorithm currently promotes m × 2ⁿ-sized structs as if they were 2ⁿ-aligned since there's not much reason to group them with smaller-aligned fields. But this isn't guaranteed (like pretty much everything).

Can you elaborate on when you'd use this instead of, say, repr(linear)?

How often is it one-way-compatible vs something like repr(transparent)? I could also imagine a simpler thing here like saying that (A, B) and (TransparentAroundA, B) have the same layout, without opening the more complicated doors.

I assume this is about concrete instances of generic fields having different sizes/alignments and so there being no optimal choice when you place them manually in a linear layout.

So for Foo<T>{a: u16, b: T} the layout should be

  • {a, b} for T=u8 and T=i8
  • {b, a} for T=usize and T=&u8