Foreign Function Interface (FFI)

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:

  1. Calling Convention (ABI): Agreeing on which CPU registers and stack offsets hold function arguments and return values (extern "C").
  2. Memory Layout: Using #[repr(C)] and core::ffi::c_* types so both sides agree on struct field offsets, alignment, and integer widths.
  3. String Representation: Converting between Rust's length-prefixed UTF-8 str/String and C's null-terminated CStr/CString (c"...").
  4. Ownership & Allocator Matching: Ensuring memory allocated by Rust's allocator is freed by Rust, and memory allocated by C's malloc is freed by C's free.
  5. 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