Foreign Function Interface (FFI)

Calling C Functions from Rust

To call a C function from Rust, you declare the foreign function's signature inside an unsafe extern "C" block using C-compatible parameter and return types from core::ffi. At link time, the native linker resolves the declared symbol name against the C standard library (libc / msvcrt) or a linked native library.

Rust Source Code:
  unsafe extern "C" {
      fn abs(input: c_int) -> c_int;
  }
       |
       v  (Compiled by `rustc`, resolved by system linker against `libc`)
Native C Library (`stdlib.h`):
  int abs(int j);

Why Foreign Blocks Are Marked unsafe extern "C" in Rust 2024

In the Rust 2024 edition, declaring an extern block requires the unsafe keyword (unsafe extern "C" { ... }). Declaring a foreign signature is itself an unsafe promise by the programmer that the name, parameter types, return type, and calling convention match the actual compiled C symbol—because if the declared signature mismatches the C definition, calling the function causes immediate undefined behavior.

Inside an unsafe extern "C" block:

  • Functions without a qualifier (fn strlen(s: *const c_char) -> usize;) are unsafe to call and must be invoked inside an unsafe { ... } block.
  • Functions that are guaranteed to be sound for all possible argument values (such as pure mathematical functions on integers or floats with no pointer parameters or global state hazards) can be annotated with safe fn, allowing Rust code to call them without an unsafe { ... } block at every call site!
use core::ffi::{c_char, c_double, c_int};

unsafe extern "C" {
    // 1. Marked `safe fn` because `abs` and `sqrt` accept all values of their
    //    primitive types without ever causing memory unsafety or UB:
    safe fn abs(input: c_int) -> c_int;
    safe fn sqrt(x: c_double) -> c_double;

    // 2. Unqualified `fn` remains `unsafe` to call because passing a null,
    //    dangling, or non-null-terminated pointer causes Undefined Behavior:
    fn strlen(s: *const c_char) -> usize;
}

fn main() {
    // Calling `safe fn` items from the `unsafe extern "C"` block requires no `unsafe` block:
    let magnitude = abs(-42);
    let root = sqrt(144.0);
    println!("C abs(-42) = {magnitude}, C sqrt(144.0) = {root:.1}");

    // Calling `strlen` requires an `unsafe` block and a valid null-terminated C string:
    let c_literal = c"Rust 2024 FFI";
    // SAFETY: `c_literal.as_ptr()` points to a valid, statically allocated,
    // null-terminated byte string of length 13.
    let len = unsafe { strlen(c_literal.as_ptr()) };
    assert_eq!(len, 13);
    println!("C strlen(c\"Rust 2024 FFI\") = {len}");
}
C abs(-42) = 42, C sqrt(144.0) = 12.0
C strlen(c"Rust 2024 FFI") = 13

Renaming Foreign Symbols with #[link_name = "..."]

When a C function name collides with a Rust keyword or follows non-snake-case naming conventions, attach #[link_name = "actual_c_symbol"] to bind a clean Rust function name to the exact underlying C linker symbol:

use core::ffi::c_int;

unsafe extern "C" {
    #[link_name = "abs"]
    safe fn c_absolute_value(val: c_int) -> c_int;
}

fn main() {
    println!("c_absolute_value(-99) = {}", c_absolute_value(-99));
}
c_absolute_value(-99) = 99