Skip to content

← Performance and Data Layout step 5 of 20

Medium Primitives

Compare Rust and C struct layouts

Compare four pairs of structs with identical fields. Give one member of each pair C representation and leave the other with Rust's default representation, then measure their sizes and alignments.

pub fn compare() -> Vec<(usize, usize)>

Add #[repr(C)] to CSmall, CWide, CMixed and CStagger. Keep the R* definitions unchanged, with no representation attribute. Preserve all fields and their declaration order. Use size_of and align_of in the supplied function to return eight (size, alignment) pairs in this order:

CSmall, RSmall, CWide, RWide, CMixed, RMixed, CStagger, RStagger

All measurements are in bytes. A name starting with C does not affect layout; the attribute is what changes the representation.

Calculate the C layout

For these structs, which have no packing or explicit alignment modifier, repr(C) places fields in declaration order. Each field starts at an offset divisible by its alignment. The total size rounds up to a multiple of the largest field alignment.

For { a: u8, b: u32, c: u8 } on a target with u32 alignment four:

Bytes Contents
0 a
1..=3 Interior padding
4..=7 b
8 c
9..=11 Trailing padding

The fields occupy six bytes and the padding occupies six more, for a total size of twelve. Trailing padding keeps successive array elements aligned.

Observe the default Rust layout

Rust's default representation can reorder fields. For example, placing the four-byte field first can reduce this struct's size from twelve to eight bytes. It is an optimization the compiler can make, not a field-sorting algorithm the language promises.

The following sizes are observations from Rust 1.98.1 on the verification host, where the integer alignments are 1, 2, 4 and 8 for u8, u16, u32 and u64. They are useful comparisons, not portable constants for Rust representation:

Field types in declaration order C size Observed Rust size
u8, u32, u8 12 8
u8, u64, u16 24 16
u16, u8, u16, u8 8 6
u8, u16, u8, u32 12 8

The reductions differ: the third pair saves two bytes per element, while the second saves eight. Multiply the measured size difference by the element count to estimate an array's storage difference.

Know what is guaranteed

Default Rust layout must align fields correctly and avoid overlapping the storage of nonzero-sized struct fields. The struct's alignment is at least that of its most strongly aligned field. These constraints do not promise:

  • Declaration-order placement or any particular sorting algorithm.
  • The smallest possible size.
  • A size no larger than the corresponding C representation.
  • Alignment exactly equal to that of the corresponding C representation.
  • Identical layouts for separately declared types with matching fields.

The C results also depend on the target's primitive alignments. In particular, the fixture for CWide assumes u64 alignment eight; some targets use a smaller alignment. repr(C) defines a layout rule for the target rather than one universal byte layout for every platform.

Interpret this exercise's checks

The harness checks eight returned pairs, pins each C size and alignment, and checks that the measured Rust size is no larger and its alignment matches. Those last two checks are observations expected by this exercise's compiler/target setup, not language guarantees. They may need review when changing the toolchain, target or layout-randomization settings.

Use the compiler queries as requested. Do not hardcode the observed Rust sizes to satisfy these checks. In production tests, distinguish a deliberate platform-specific requirement from a guarantee intended to hold on every supported compiler and target.

Apply layout information carefully

C representation can help match a foreign struct, but FFI also requires compatible field types, value validity, ownership and calling conventions. Do not assume default Rust layout matches a foreign declaration.

Neither representation is automatically a file or network format. Padding, byte order, pointers and valid-value constraints still need handling. Encode fields explicitly when a format must be portable.

For a C-layout type you control, changing field order can reduce padding. Once another component depends on that layout, reordering is an ABI change. Default Rust layout may already reduce padding, but measure the actual types rather than assuming every ordering or representation has the same cost.

Run the comparison and check the reported measurements. Passing requires compilation, the expected results for this exercise and the Clippy gate.