Types like (u32, u8) have padding. Types like u8 have alignment of 1. Why not make ((u32, u8), u8) the same size as (u32, u8) by using the padding? It would lower RAM usage by a lot.
This would make tuples not safe to memcpy, but thankfully nobody does that...
Every move is effectively a memcpy, including assignment, even though it doesn't have to be a literal call to libc::memcpy.
If you pass a &mut (u32, u8) somewhere, it has to be safe to write that without knowing outer context, like whether there might be data lurking in the padding.
The "downside" can be solved with an optimization target: an "-Olowmem" equivalent maybe. (yeah changing struct sizes based on optimization targets is kinda weird but only kinda. maybe some other codegen flag tho?) It's also possible the gains in memory usage could increase performance beyond the losses in code size, so that's also something to keep in mind.
Use-cases include embedded systems and web browsers.
In case if you confused why I shared the C code here, the Rust object/memory model is not formally specified yet but many of us believes it will inherit "what C does" with modifications. So it would make sense to assume "what C does" for cases like this where we've not made much decisions (yet).
Memory in Rust (AFAIK) is not typed - only accesses are typed. So, it's legal for unsafe code to transmute a &mut (u32, u8) to a &mut [u8; 8], and write through the new pointer.
Also, as a practical consideration: forbidding writes that affect padding would mean that Rust can never emit a 64-bit move for a write to a &mut (u32, u8). Instead, it would need to emit a 32-bit move and and 8-bit move (so that it doesn't touch the three bytes of padding).
No it's not. &mut T is invalid if it points to invalid T, and u8 is invalid if uninitialized. It is mentioned that the padding between the fields are considered uninitialized.
It's not about what you, the user of the inner (u32, u8), may read. It's a problem if your write clobbers the extra data of someone's outer ((u32, u8), u8) which occupied that padding.
Guaranteed zero-padding makes padding unusable for extra fields. On the other hand, guaranteed not-touch-the-padding makes the padding usable for extra fields, which may include but is not limited to the enum tag.
That may be less efficient then what can already be generated. For (u32, u8), perhaps (since that's 32-bit read/write+8-bit read/write), but for say (String, u8), which is 4 usize, It could easily be worse to emit a memcpy for only the first 3, then a 8-bit read, rather than simply a 4*size_of::<usize> memcpy, which can just be an avx2 move or two sse moves (and only 1 on ix86), vs. 1 sse move, one qword move, and one byte move.
Example codegen (edit, fixed to use intel syntax instead of AT&T but not actually):
I definitely think there's value to allowing Rust to exploit the padding of structs. However, I think it would need to be opt-in (e.g. an attribute to disable taking a reference to a field) to preserve performance, and to avoid breaking unsafe code.
It's all the same problem, though. The thing that currently blocks it is reading and writing through references. So the solution is to disallow those references, at which point the compiler can emit whatever code it needs to in order to handle the overlap.
struct Foo { move x: u8, move y: (u8, u16) } would allow smart overlaying of those structs in the same way it would allow arithmetic coding of multiple enum fields.
This is definitely not true in general - for example, a program that performs a large number of reads/writes from a fixed number of locations in memory.
Enabling this flag would break any unsafe code that performs any reads/writes through a differently typed pointer of equivalent size (e.g. *mut [u8; 8] or *mut [MaybeUninit<u8>; 8])