Type layout¶
Level: 301 · deep dive
One line: The default repr(Rust) promises almost nothing about where fields sit — the compiler may reorder them, so struct { a: u8, b: u32, c: u8 } is 8 bytes where #[repr(C)] makes it 12 — and repr(C), repr(packed), repr(align) and the niche optimisation are how you trade that freedom for a layout you can rely on.
Stub — an outline, not a lesson. There is no runnable example behind this page yet, so nothing on it has been through the check that backs every other claim in this library. The bullets below are the questions the finished page has to answer.
What it has to cover¶
repr(Rust): the Reference ↗ says "the only data layout guarantees made by this representation are those required for soundness" — each field's offset is divisible by its alignment, the type's alignment is at least its largest field's, and fields do not overlap — and "the ordering does not have to be the same as the order in which the fields are specified". The finished page printssize_ofand each field's offset withoffset_of!(stable since 1.77.0) for the three-field struct under each representation: 8 bytes, 12 withrepr(C), 6 withrepr(C, packed).- May two instantiations of one generic struct,
Pair<u8, u32>andPair<u32, u8>, get different field orders? What else isrepr(Rust)free to change between compiler versions? repr(C): declaration order and C's padding rules, and why that makes a layout a contract — for FFI, for writing bytes to a file, for aunion.repr(packed): no padding, alignment 1, and the cost —&packed.bisE0793, "reference to field of packed struct is unaligned … creating a misaligned reference is undefined behavior (even if that reference is never dereferenced)". Clippy'srepr_packed_without_abi(warn) asks forrepr(C, packed)orrepr(Rust, packed)so the intent is explicit.repr(align(N)): raising alignment —#[repr(align(64))]on au64wrapper gives size 64 and array elements 64 bytes apart, the tool False sharing needs.- Enums:
repr(u8)and friends fix the discriminant's type;repr(C)on a fieldful enum gives the tag-plus-union layout What a union is describes;repr(transparent)makes a one-field wrapper ABI-identical to the field. rustc's deny-by-defaultconflicting_repr_hintsrefuses incompatible combinations — which ones? - Niches, measured on this machine:
Option<&u8>8 bytes,Option<*const u8>16,Option<bool>1,Option<char>4,Option<NonZeroU32>4,Option<u32>8,Option<extern "C" fn()>8. Where doesNonego in each, and which of these does std guarantee rather than merely do? - Padding bytes are uninitialised — so writing a struct's memory to a file sends bytes nobody set, which is the C library's A record on the wire ↗ in Rust.
The trap it exists for¶
Assuming Rust keeps fields in declaration order, as C does, and then writing a repr(Rust) struct's bytes to disk, passing it to C, or transmuting it into a "matching" struct. Every size matches, nothing warns, and the layout the code depends on was never promised by any version of the compiler.
Where this sits¶
- What a union is covers overlapping fields; Nullable pointers covers the niche for
Option<Box<T>>and the raw-pointer exception; What is a record, in memory? covers choosing a shape for data; Calling C introduces#[repr(C)]in one section. This page covers only the layout rules. STRUCTS.md lists layout, padding andrepras missing; this is that page's outline.
See also¶
- What a union is —
repr(C)unions, and the enum as a tagged union - Nullable pointers — the niche, and why
Option<*const T>is 16 bytes - What is a record, in memory? — six shapes for one row, sized
- Calling C — "a struct needs
#[repr(C)], or it is not a layout" - FFI-safe types — layout as a contract with another compiler
- False sharing —
repr(align)used for speed mem::transmute— the operation that most often assumes a layout- STRUCTS.md · GLOSSARY.md — the structs map, and niche
If you are coming from another language¶
- C. Declaration order is guaranteed, padding is inserted to satisfy alignment,
offsetofreports it, and__attribute__((packed))or#pragma packremoves it —repr(C)is exactly that model, andrepr(Rust)is what you get without asking. A record on the wire ↗ prints the offsets and the padding a struct sends when its memory is written straight to a file. - Python.
struct.packwith the default@prefix uses native sizes and alignment, padding included, while<,>and=use standard sizes with no padding;ctypes.Structurefollows the C compiler, with_pack_to change it. The encodings library's Packing a record ↗ lays the four languages' answers side by side. - Go. The gc compiler keeps declaration order, and
unsafe.Sizeof,AlignofandOffsetofaresize_of,align_ofandoffset_of!. Reordering fields to save padding is left to you — thefieldalignmentanalyzer suggests it — whererepr(Rust)does it for you and takes away the order in exchange. - ABAP. A structure in a Unicode system carries alignment gaps before fields that need them, and
ASSIGN … CASTINGonto a structure is checked against the fragment views of source and target, so character and numeric regions have to line up. That check is the ABAP runtime refusing the mistake this page's trap describes; Rust'stransmutechecks the total size and nothing else.
Po polsku¶
Domyślna reprezentacja repr(Rust) nie obiecuje kolejności pól: kompilator może je przestawić, żeby zmniejszyć wypełnienie (padding), więc struct { a: u8, b: u32, c: u8 } zajmuje 8 bajtów, a z #[repr(C)] — 12. repr(C) daje kolejność deklaracji i reguły wyrównania (alignment) z C, repr(packed) usuwa wypełnienie za cenę zakazu referencji do pól (E0793), a repr(align(N)) podnosi wyrównanie. Nisza (niche) to niedozwolony wzorzec bitów, który kompilator wydaje na None — dlatego Option<&u8> ma 8 bajtów, a Option<*const u8> już 16.
Szukaj po polsku: układ struktury w pamięci · wyrównanie i wypełnienie · optymalizacja niszy · rust repr C vs repr Rust field order · rust repr packed E0793 · rust niche optimization Option size