We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Performance and Data Layout step 7 of 20
The other reprs: transparent, packed, align, integer enums
#[repr(C)] gets you the standard ABI. Matching an existing C header
usually needs more than that, and the remaining representation attributes are
the tools. They are also where alignment stops being an abstract property and
starts being a compile error.
The header you are matching
#pragma pack(push, 1)
struct wire_header {
uint8_t version;
uint32_t length;
uint8_t flags;
uint16_t checksum;
};
#pragma pack(pop)
typedef struct { int32_t raw; } handle_t;
enum status : uint8_t { STATUS_OK = 0, STATUS_RETRY = 1, STATUS_FATAL = 2 };
#[repr(C, packed)]
packed removes all padding: every field goes at the next byte, and the
struct’s alignment becomes 1. wire_header under normal C rules would be 12
bytes; packed it is exactly 8 — the sum of its fields, which is what a
wire format needs.
Write #[repr(C, packed)], not #[repr(packed)]. packed on its own
removes the padding but leaves the field order unspecified, so you get a
compact struct that still does not match the header. clippy’s
repr_packed_without_abi is on by default and says exactly this.
The error that makes the lesson stick
let r = &h.length; // error[E0793]
error[E0793]: reference to packed field is unaligned
= note: packed structs are only aligned by one byte, and many modern architectures
penalize unaligned field accesses
= note: creating a misaligned reference is undefined behavior (even if that
reference is never dereferenced)
Every &T in Rust carries a promise that the address is aligned for T, and
the optimiser relies on it. In a packed struct that promise cannot be kept, so
the reference is forbidden — even if you never dereference it.
Note what this catches that you would not expect: println!("{}", h.length)
fails too, because the formatting macros take references to their arguments.
Two ways out:
-
Copy the value out.
let n = h.length;is fine — reading aCopyfield by value creates no reference. This is the idiomatic fix and it is entirely safe. -
Take a raw pointer and read it unaligned. For fields you cannot copy out, or when you need the address:
unsafe { (&raw const h.checksum).read_unaligned() }&raw const place(stable syntax since 1.82, replacingaddr_of!) makes a raw pointer without ever forming a reference, andread_unalignedreads through it with no alignment requirement.
Both appear in this problem, deliberately: read_length uses the safe
copy-out, read_checksum uses the raw-pointer form so you have written it
once.
Packed access can be slower, and on some architectures it traps. x86 and aarch64 tolerate unaligned loads with a penalty; not every target does. Pack for wire formats, not for compactness in memory you own.
#[repr(transparent)]
#[repr(transparent)]
pub struct Handle(pub i32);
“This newtype has exactly the layout and ABI of its single field.” Size 4,
alignment 4, passed in registers identically to a bare i32. It is the
idiomatic zero-cost way to add type safety to an FFI handle: the C side sees
an int32_t, your side cannot confuse a Handle with a FileNo.
The rule is that the type may contain exactly one field of non-zero size.
Zero-sized fields (PhantomData, ()) are allowed alongside it — which is
how phantom-typed handles work.
#[repr(u8)] on an enum
#[repr(u8)]
pub enum Status { Ok = 0, Retry = 1, Fatal = 2 }
Fixes the discriminant to a u8: size 1, alignment 1, and the values are
exactly the ones you wrote. Without it, rustc picks whatever width it likes.
s as u8 gives you the discriminant back.
And a small piece of magic to notice: Option<Status> is also 1 byte. The
enum uses three of 256 patterns, so None hides in one of the other 253 — a
niche, exactly as in the previous item.
What to write
Define the three types with the right representations, then:
pub fn read_length(h: &WireHeader) -> u32 // copy the field out
pub fn read_checksum(h: &WireHeader) -> u16 // &raw const + read_unaligned
pub fn status_code(s: Status) -> u8
pub fn handle_raw(h: Handle) -> i32
pub fn describe() -> Vec<(String, usize, usize)>
describe returns (name, size, align) for WireHeader, Handle, Status
and Option<Status>, in that order. These are repr-pinned types, so unlike
the previous items the exact numbers are guaranteed and the test asserts
them: 8/1, 4/4, 1/1, 1/1.
Errors and lints in this neighbourhood
- E0793 — reference to a packed field. Covered above.
-
E0587 — a type cannot have both
packedandalignrepresentation hints. They contradict each other, and rustc says so. -
E0517 —
#[repr(..)]applied to something that cannot carry it (a function, ause, a statement). - E0084 — an enum with no variants cannot have a discriminant representation.
-
enum_clike_unportable_variant— clippy correctness, denied by default: a C-like enum whose discriminant does not fit in the target’sisize. -
default_union_representation,trailing_empty_array— pedantic, but the FFI-shaped mistakes they catch are real.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.