Course contentsShow
← Rust
Lesson 1622 of 1,91014. System Integration and EcosystemFree lesson

FFI-Safe Struct Layouts

You will apply repr(C) and repr(transparent) to ensure binary compatibility with external headers.

In this lesson, you will learn how to enforce binary-compatible memory layouts in Rust to ensure your data structures align perfectly with external C headers.

When you pass a struct across an FFI boundary, Rust’s default layout algorithm is unreliable. Rust reserves the right to reorder fields to optimize for space—a behavior that causes catastrophic misalignment when a C-compiled library expects a specific, fixed order. To communicate across language barriers, we must exert explicit control over memory.

The #[repr(C)] Attribute

Applying #[repr(C)] is the most critical step for cross-language compatibility. It instructs the Rust compiler to use the same field-ordering rules as the C ABI (Application Binary Interface) for the target platform. Without this, your struct fields might be shuffled, leading to corrupted data when accessed by external code.

#[repr(C)]
pub struct SensorData {
    pub id: u32,
    pub timestamp: u64,
    pub value: f32,
}

In the snippet above, the compiler guarantees that id comes first, followed exactly by timestamp, and then value, matching the layout a C struct would have.

The #[repr(transparent)] Attribute

Sometimes you need to wrap a single type (like a raw pointer or a primitive) in a newtype for type safety, but you need the compiler to treat the wrapper as if it were the inner type itself. This is where #[repr(transparent)] shines. It ensures that the struct has the exact same representation, alignment, and size as its single field.

#[repr(transparent)]
pub struct Handle(u64); // Binary compatible with a raw u64

extern "C" {
    fn process_handle(h: Handle);
}

By using #[repr(transparent)], you keep the binary representation strictly identical to u64, allowing you to pass Handle directly into C functions that expect an integer, while maintaining strong type safety on the Rust side.

These attributes are not mere suggestions; they are directives that strip away Rust's internal layout optimizations in favor of predictable, standard memory arrangements. When you bridge the gap to C, these layout guarantees ensure that memory offsets are identical on both sides of the call stack.

Key Takeaway: Use #[repr(C)] to lock field order for multi-field structs and #[repr(transparent)] for single-field wrappers to guarantee binary compatibility with external C APIs.