tezvyn:

Why must FFI-bound structs use #[repr(C)] and what breaks without it?

AI-drafted, machine-checkedSource: doc.rust-lang.orgintermediate
WHAT IT TESTS

Rust ABI stability across FFI.

ANSWER OUTLINE

repr(C) fixes field order, size, alignment to C rules for extern calls; omitting it lets Rust reorder or pad fields, causing UB.

RED FLAG

Believing default layout is stable or repr(C) is optional.

WHAT THIS TESTS: This question tests whether you understand that Rust's default struct layout is not stabilized and that FFI requires a predictable ABI. The interviewer wants to see that you know repr(C) is not decorative but a semantic requirement for sound interoperability with C code. They are also checking if you recognize that layout mismatches cause undefined behavior rather than mere performance penalties.

A GOOD ANSWER COVERS: A strong answer starts by stating that repr(C) instructs the compiler to lay out the struct exactly as C or C++ would, fixing field order, size, and alignment. Next, it explains that omitting it leaves the layout unspecified, meaning the compiler may reorder fields, insert padding differently than C expects, or choose an alignment that does not match the foreign side. Third, it notes that passing a default-layout Rust struct across an extern boundary leads to the foreign code reading garbage from wrong offsets, which is immediate undefined behavior. Finally, a senior candidate mentions edge cases: zero-sized types remain zero-sized under repr(C) even though C++ empty structs consume one byte, wide pointers and tuples are never FFI-safe even with repr(C), and enums with fields require special bridging.

COMMON WRONG ANSWERS: A red flag is claiming that Rust's default layout is stable or deterministic enough for FFI. Another is saying repr(C) is only for performance or cache locality. Some candidates confuse repr(C) with repr(transparent), arguing that a single-field wrapper is enough; while repr(transparent) is useful for newtypes, it is not the general solution for multi-field structs. Finally, suggesting that you can safely transmute or pointer-cast a default-layout Rust struct to a C struct shows a dangerous misunderstanding of the language guarantees.

LIKELY FOLLOW-UPS: The interviewer may ask how you would handle a struct containing a String or Vec across FFI, which should be answered by converting to raw pointers or FFI-safe types before crossing the boundary. They might ask about repr(u8) on fieldless enums, or how to guarantee layout for tagged unions. Another follow-up is whether repr(C) alone makes a type FFI-safe, prompting a discussion of DSTs, tuples, and ZSTs.

ONE CONCRETE EXAMPLE: Imagine a Rust struct Point { x: u32, y: u8 } without repr(C). The compiler might reorder fields or add trailing padding so the size becomes 8 bytes with y at offset 4. On the C side, a struct with the same declaration could place x at offset 0, y at offset 4, and have size 8, which happens to match on some targets but is not guaranteed. If Rust instead inlines or reorders y before x, the C code reading pointer->x would actually read the y byte plus three bytes of padding, corrupting the value. Adding repr(C) locks the layout to the platform C ABI, making the mapping sound.

Read the original → doc.rust-lang.org

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.