Under The Hood
backend rust Rust 1.x (extern blocks, bindgen)

FFI: extern "C", bindgen, and Zero-Overhead C Interop

Last updated
Prerequisites:
Rust safety and unsafe blocks
Understanding of C functions and structs
  • rust
  • ffi
  • extern-c
  • bindgen
  • c-interop
  • abi

Read at your depth

The practical view

FFI (foreign function interface) lets Rust call C functions: extern "C" { fn strlen(s: *const c_char) -> usize; } and call them inside unsafe blocks. The extern "C" declares the C ABI (calling convention). Use std::ffi::CString to create null-terminated strings, c_char/c_int from std::os::raw. bindgen (a tool) generates Rust declarations from C headers automatically: bindgen header.h -- -I. The generated bindings are used in a build.rs via the bindgen crate. All FFI calls are unsafe (the C side isn't checked by Rust's guarantees). For structs, use #[repr(C)] to match the C layout.

The same idea in other frameworks

react equivalent
N/A — Rust language feature.

Legacy vs modern

Hand-written extern declarations vs bindgen-generated bindings

Hand-declaring FFI is error-prone (wrong types/layouts); bindgen generates correct bindings from the C headers.

before → after
Hand-written extern block (fragile)
extern "C" {
  fn compress(input: *const u8, len: usize, out: *mut u8) -> i32; // ❌ OLD: guessed signature
bindgen-generated bindings
// ✅ NEW: generated by bindgen from the C header — correct types/layout
pub struct Header {
    pub kind: u8,
    pub size: u64, // ✅ NEW: #[repr(C)] layout matches C
}

Interview gotchas

Context

This tests the safety boundary — interviewers want the invariants the Rust side must uphold when calling unmanaged C code, and how to make it sound.

The mechanical answer

FFI calls are unsafe because the C function operates outside Rust's guarantees: it may not follow the aliasing rules (mutating data through a shared reference), may not validate pointers (dangling/null), may assume a different lifetime, or may not be thread-safe. The compiler cannot verify any of this. A sound FFI wrapper is a SAFE function that internally does the unsafe call while upholding the invariants: the input pointers must point to valid, aligned, initialized data for the call duration; the output buffer must have sufficient capacity; the data must not be aliased in a conflicting way; and the C function must not retain the pointers beyond the call (unless documented and handled). The wrapper's doc comments should state the safety contract, and tests (including Miri where possible) should exercise the edge cases. If the C code can be given dangling pointers or overlapping buffers, the wrapper must prevent or document that.

Trap

The naive answer is 'unsafe just means the call might crash'. Unsafe in FFI is a contract: if you pass a dangling pointer or wrong lifetime, the C code may corrupt arbitrary memory (not just crash), and the corruption may surface in unrelated Rust code. Another trap: assuming the C function is 'trusted' because it's a well-known library — even well-known C libraries have edge cases (null returns, error paths) that violate your assumptions. Candidates who describe the full safety contract show real understanding.

Under The Hood — a multi-depth technical interview hub.

Third-party licenses Press ⌘ K to search.