Course contentsShow
← Rust
Lesson 1413 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Passing Structs across FFI Boundaries

You will align Rust struct fields with C-compatible ABI layouts for external function communication.

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:

  1. Field Types: Ensure your struct fields are themselves FFI-safe. Use types like i32, u64, f64, or raw pointers. Avoid high-level Rust types like String, Vec<T>, or Option<Box<T>>, as their internal representations are not compatible with C.
  2. 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.
  3. 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.