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

The Foreign Function Interface ABI

You will understand the Application Binary Interface requirements for calling into shared libraries across languages.

In this lesson, you will learn how the Application Binary Interface (ABI) dictates the rigid contract between Rust and foreign code, ensuring they can communicate across language boundaries without memory corruption.

The ABI as a Contract

When you call into a shared library (like a C .so or .dll), the compiler cannot rely on Rust-specific conventions. The ABI is the "rulebook" that specifies how function arguments are passed, how return values are stored, and how stack frames are managed.

In Rust, the default ABI is not stable, meaning it can change between compiler versions to accommodate optimizations. To interact with other languages, we must use extern "C". This tells the compiler: "Do not use Rust's internal layout logic; use the platform's C-compatible binary convention instead."

Memory Layout and Calling Conventions

The ABI defines physical reality for your code. Consider passing a struct across an FFI boundary:

#[repr(C)]
struct ExternalData {
    id: u32,
    value: f64,
}

extern "C" {
    fn process_data(data: *const ExternalData);
}

By using #[repr(C)], you force the compiler to follow the C-standard memory layout. Without it, the Rust compiler might reorder fields or add padding to maximize performance, which would lead to the foreign C library reading garbage data or causing a segmentation fault.

Beyond layout, the ABI defines the calling convention. For example, it dictates whether arguments are pushed onto the stack or placed in specific CPU registers (like rdi, rsi on x86_64 Linux). If the caller and callee disagree on which register holds the first argument, the program will experience undefined behavior—a frequent source of "heisenbugs" in cross-language systems.

Ensuring Compatibility

When designing an FFI boundary, you are essentially building an API that ignores the safety guarantees of the Rust borrow checker. Because the foreign side is unaware of Rust’s ownership model, you must ensure that your data remains valid for the duration of the call. Never pass Rust-managed pointers that might be dropped or moved while the foreign function is still executing.

Key Takeaway: The ABI defines the binary-level protocols for register usage and memory layout, requiring the extern "C" calling convention and #[repr(C)] attribute to establish a reliable, stable contract between languages.