We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 5 of 20
repr(Rust) reorders your fields — and that's the point
“Add #[repr(C)] for FFI” is a rule people memorise without ever seeing why.
This item makes you watch the compiler silently rearrange your struct, which
converts the rule into an observation — and hands you a genuine optimisation
insight on the way.
The layout algorithm C uses, and Rust does under repr(C)
Fields sit in declaration order. Each one goes at the next offset that is a multiple of its own alignment, inserting padding to get there. The struct’s alignment is the maximum of its fields’ alignments, and the total size is rounded up to a multiple of that.
struct { uint8_t a; uint32_t b; uint8_t c; };
a at 0. b needs alignment 4, so three bytes of padding and b at 4.
c at 8. Size so far 9, rounded up to a multiple of 4 → 12 bytes, three of
them wasted, four of them padding in total.
What Rust does when you do not say repr(C)
Whatever it likes. Here, it sorts the fields by descending alignment, puts
b first, then packs a and c into the tail:
| struct |
repr(C) |
repr(Rust) |
|---|---|---|
{ u8, u32, u8 } |
12 | 8 |
{ u8, u64, u16 } |
24 | 16 |
{ u16, u8, u16, u8 } |
8 | 6 |
{ u8, u16, u8, u32 } |
12 | 8 |
A third of the memory, for free, on every one of them. On a Vec of a million
elements that is four megabytes you were not going to notice you had lost.
This is not a theoretical liberty the compiler might exercise. It demonstrably does, today, on this toolchain, on the simplest structs you can write.
What to write
pub fn compare() -> Vec<(usize, usize)>
Four pairs of structs are declared for you. Within each pair the fields and
their order are identical — the only difference should be the representation.
Right now both members of every pair are repr(Rust). Mark the C* ones
correctly, and return (size_of, align_of) for all eight, in the order:
CSmall, RSmall, CWide, RWide, CMixed, RMixed, CStagger,
RStagger.
How the tests are written, and why you should copy the habit
The grader asserts three things per pair:
-
the
repr(C)size and alignment exactly — these are guaranteed by the ABI, so pinning them is legitimate and will still be true in five years; -
repr(Rust)size is not larger thanrepr(C)size — a relationship, not a number; - both have the same alignment — also guaranteed, since alignment is determined by the fields.
It deliberately does not assert that RSmall is 8 bytes. It is, today. It
is not promised to be, and a test that pins it would be a test that breaks on
a compiler upgrade for no reason. Assert what is guaranteed; observe the
rest.
The rule this makes non-negotiable
Never transmute a repr(Rust) type across an FFI boundary, never memcpy one
into a file or a socket, and never assume two repr(Rust) types with the same
fields have the same layout — even the same type in two compilations of the
same program carries no promise. It “works” right up until it does not, and
the failure is silent corruption rather than a crash.
And do not depend on the specific reordering you just measured. Sorting by descending alignment is what rustc happens to do; it is not specified, it has changed before, and randomised layout exists as an unstable compiler flag precisely so that code depending on it can be caught.
#[repr(C)] is for stable layout, not for speed. As the table shows, it
usually makes your struct bigger.
The optimisation insight
Since the compiler is already sorting your fields, you get the packing benefit
for free in normal Rust. But the moment you write #[repr(C)] — for FFI, for
a wire format, for a memory-mapped structure — field order becomes yours to
get right. Declare from widest alignment to narrowest and the padding mostly
disappears: { u32, u16, u8, u8 } is 8 bytes where { u8, u32, u8, u16 } is
12.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.