In this lesson, you will learn how to design Rust structs that maintain a predictable memory layout, ensuring they can be safely passed to and from C code.
When you define a standard struct in Rust, the compiler reserves the right to reorder your fields to optimize memory usage and padding. This is a "feature" of Rust, but it is a disaster for Foreign Function Interface (FFI) communication, where a C library expects fields to appear in the exact order you defined them.
To force Rust to behave like C, we use the #[repr(C)] attribute.
Applying repr(C)
By annotating your struct with #[repr(C)], you instruct the Rust compiler to use the platform's standard C Application Binary Interface (ABI) for that type. This guarantees that fields are laid out in memory sequentially, exactly as they are written in the source code.
#[repr(C)]
pub struct Point {
pub x: f64,
pub y: f64,
}
extern "C" {
// We can now safely pass a pointer to our struct
fn process_point(p: *const Point);
}
Critical Rules for FFI Layout
Simply adding #[repr(C)] isn't always enough if you aren't careful about types:
- Field Types: Ensure your struct fields are themselves FFI-safe. Use types like
i32,u64,f64, or raw pointers. Avoid high-level Rust types likeString,Vec<T>, orOption<Box<T>>, as their internal representations are not compatible with C. - Padding and Alignment: While
repr(C)fixes the order, the C compiler might still add padding bytes between fields to satisfy CPU alignment requirements. Rust will automatically add matching padding to keep the struct size and alignment identical to what a C compiler would produce. - Ownership: Remember that passing a struct to C does not transfer ownership unless the C API specifically states it will handle memory management (e.g., via
free). If your struct contains pointers, ensure they remain valid for the duration of the C function call.
By combining #[repr(C)] with POD (Plain Old Data) types, you create a stable, predictable interface that allows C and Rust to "speak the same language" regarding memory.
Key Takeaway: Use #[repr(C)] on any struct crossing the FFI boundary to prevent the Rust compiler from reordering fields and to ensure a memory layout compatible with C.