In this lesson, you will learn how to design Rust interfaces that remain binary-compatible across compiler updates and different library versions.
When you compile a Rust library, the compiler makes no guarantees about the layout of your data structures or the mangling of your function names between different versions of the compiler. Because Rust’s internal ABI (Application Binary Interface) is unstable, a library compiled with rustc 1.70 might be binary-incompatible with a client application compiled with rustc 1.80.
To maintain ABI Stability, you must opt out of Rust’s default layout behaviors and enforce a contract that external consumers (like C programs or other languages) can rely on.
Enforcing a Stable Layout
By default, the compiler reorders fields in your struct to optimize for memory usage. To stop this, you must use the #[repr(C)] attribute. This instructs the compiler to lay out the data structure exactly as a C compiler would, ensuring that the offset of each field remains predictable and consistent regardless of the Rust compiler version.
#[repr(C)]
pub struct PluginConfig {
pub version: u32,
pub flags: u64,
pub data_ptr: *const u8,
}
The Role of Opaque Pointers
Even with #[repr(C)], changing the size of a struct in a future version of your library will break existing binaries that expect the old memory layout. To truly guarantee ABI stability over time, use the Opaque Pointer pattern. Instead of exposing the struct definition, expose a pointer to an incomplete type or a handle:
// In your public header/API:
pub struct PluginState; // No fields defined
#[no_mangle]
pub extern "C" fn get_plugin_version(state: *const PluginState) -> u32 {
// Implementation can change internally without breaking the ABI
42
}
By keeping the struct definition hidden (opaque) from the consumer, you can change the internal implementation and field sizes of PluginState within your library without requiring your clients to recompile. They only ever handle a pointer, while your library manages the actual memory allocation and layout behind the scenes.
Key Takeaway: To ensure ABI stability, use #[repr(C)] to fix data layouts and employ opaque pointers to decouple the implementation details from the public interface.