Rolling out (or unrolling) struct field reorderings

I see two trade-offs at play here, two of them are incompatible:

  • deterministic builds (no randomization),
  • increased security (randomization),

and one consequence of no randomization + stable sort of the fields:

  • control: no randomization + stable sort allows users to pack fields that are accessed together close to each other. This allows packing hot fields in the same cache line, or packing fields in different cache lines to avoid false sharing.

So in a nutshell, no randomization gives us deterministic builds, which make debugging easier, and more control, which might result in better performance when used correctly.

However, I cannot recall the last time I was debugging something in C++ and struct layout optimization made that harder, so while I think that determinism does in theory make debugging easier, I am not convinced that the improvement is worth it in practice. The same goes for more control. Users wanting control are going to be using #[repr(C)] anyways (which I assume is not going to reorder fields if randomization is enabled).

OTOH Rust already has ASLR enabled by default, and randomized struct layout does make exploiting Rust programs significantly harder.

For these reasons I think that randomization is a better default, the advantages of determinism and control are just not worth it.

Following these thoughts, I think that a good default would be:

  • debug builds: true randomization to detect programs that assume a specific order early,
  • release builds: optimized randomization without guarantees about stably sorting the fields (to force those who want to rely on some kind of ordering to use #[repr(C)],
  • better #[repr(C)]: diagnostics/warnings about padding/alignment/sub-optimal layouts that can be easily disabled, and maybe some language level constructs to control what goes into which cache line (e.g. crossbeam solutions to this are pragmatic, hacky, and non-portable).

These rules would make struct layout very easy to teach:

The layout of struct fields in Rust is undefined. If you need to make any assumptions about struct field layout use #[repr(C)].

An alternative to #[repr(C)] could be #[repr(manual)] or something like that (but this is bikeshedding).

1 Like