What Is FFI
A Foreign Function Interface (FFI) is a mechanism that allows code written in one programming language to call functions, exchange data structures, and share memory with code compiled in another programming language. In Rust, FFI centers on the platform C Application Binary Interface (ABI)—the universal lingua franca understood by operating system kernels, C/C++ libraries, Python, Node.js, Swift, Go, and WebAssembly hosts.
+---------------------------+ +---------------------------+
| Rust Code | | C / Native / Host Code |
| - Ownership & Lifetimes | FFI Boundary | - Raw Pointers (`T*`) |
| - `#[repr(Rust)]` Layout | <===============> | - Struct Layout (`C`) |
| - `String` (UTF-8, len) | `extern "C"` ABI | - `char*` (`\0`-ended) |
| - Stack Unwinding | | - No Rust Unwind Info |
+---------------------------+ +---------------------------+
Two Directions of Rust FFI
| Direction | Mechanism in Rust 2024 | Primary Use Cases |
|---|---|---|
| Inbound (Rust calling C) | unsafe extern "C" { ... } blocks |
Calling OS system APIs (libc, Win32), hardware drivers, or mature C libraries (sqlite3, libz, libpq). |
| Outbound (C/Other calling Rust) | #[unsafe(no_mangle)] pub extern "C" fn ... |
Embedding high-performance, memory-safe Rust components inside C/C++, Python (PyO3), Swift, Kotlin, or Node.js apps. |
Core Challenges Across the FFI Boundary
Because a foreign compiler does not run Rust's borrow checker or share Rust's internal type representations, crossing an FFI boundary requires explicit translation across five dimensions:
- Calling Convention (ABI): Agreeing on which CPU registers and stack offsets hold function arguments and return values (
extern "C"). - Memory Layout: Using
#[repr(C)]andcore::ffi::c_*types so both sides agree on struct field offsets, alignment, and integer widths. - String Representation: Converting between Rust's length-prefixed UTF-8
str/Stringand C's null-terminatedCStr/CString(c"..."). - Ownership & Allocator Matching: Ensuring memory allocated by Rust's allocator is freed by Rust, and memory allocated by C's
mallocis freed by C'sfree. - Unwind Safety: Catching Rust panics (
std::panic::catch_unwind) before they cross into C stack frames that cannot unwind Rust destructors.
Zero-Overhead Function Calls Across extern "C"
Unlike language bridges that serialize data into JSON or IPC sockets, Rust's FFI compiles down to a direct machine-level call instruction with zero runtime marshaling overhead:
use core::ffi::c_int;
// Outbound FFI function callable from C or Rust using the C ABI
#[unsafe(no_mangle)]
pub extern "C" fn rust_saturating_add(a: c_int, b: c_int) -> c_int {
a.saturating_add(b)
}
fn main() {
let result = rust_saturating_add(1_000_000, 2_500_000);
println!("Result across extern \"C\" ABI: {result}");
}
Result across extern "C" ABI: 3500000