Foreign Function Interface (FFI)

Linking Native Libraries

When a Rust crate declares foreign functions in an unsafe extern "C" block that live outside the default C standard library, the compiler and linker must be told which native library to link (-l) and where to find it (-L). Rust supports configuring native linkage via the #[link(...)] attribute, build.rs instructions, and the -sys crate convention.

+-------------------------------------------------------------------+
|                  Native Library Linking Pipeline                  |
|                                                                   |
|  1. System Discovery (`pkg-config` or `vcpkg` in `build.rs`)      |
|     OR Bundled C Compilation (`cc` crate in `build.rs`)           |
|                           |                                       |
|                           v                                       |
|  2. Emit `cargo::rustc-link-search=native=...`                    |
|     Emit `cargo::rustc-link-lib=static=...` or `dylib=...`        |
|                           |                                       |
|                           v                                       |
|  3. `rustc` passes `-L` and `-l` to system linker (`cc` / `lld`)  |
+-------------------------------------------------------------------+

1. The #[link(...)] Attribute on extern Blocks

For standard operating system libraries or frameworks that live in default system search paths, attach #[link(name = "...", kind = "...")] directly to the unsafe extern "C" block:

kind Value Platform Artifact Linked Description
"dylib" (default) lib<name>.so / lib<name>.dylib / <name>.dll Dynamically linked shared library resolved at program startup.
"static" lib<name>.a / <name>.lib Statically linked archive embedded directly into the Rust binary.
"framework" <name>.framework (macOS / iOS only) Apple macOS/iOS framework bundle (-framework CoreFoundation).
"raw-dylib" <name>.dll (Windows only) Links against a Windows DLL directly by symbol name without requiring an import .lib file!
use core::ffi::c_double;

// On Unix systems, the math library (`libm`) provides `hypot(x, y)`:
#[cfg_attr(unix, link(name = "m"))]
unsafe extern "C" {
    safe fn hypot(x: c_double, y: c_double) -> c_double;
}

fn main() {
    println!("hypot(3.0, 4.0) = {:.1}", hypot(3.0, 4.0));
}
hypot(3.0, 4.0) = 5.0

2. Dynamic Discovery and Bundled C Compilation in build.rs

When linking third-party C libraries (such as libz, libsqlite3, or libssl) whose installation path varies across Linux distributions, macOS Homebrew, and Windows, emit linker instructions from build.rs instead of hardcoding #[link]:

  • System library discovery (pkg-config crate): Queries the system's pkg-config database and automatically prints the required cargo::rustc-link-search and cargo::rustc-link-lib lines.
  • Bundled C/C++ compilation (cc crate): Compiles .c or .cpp files shipped inside your crate's source tree into a static archive in $OUT_DIR and links it automatically.
  • Automated binding generation (bindgen crate): Parses a .h C header file and generates the exact Rust #[repr(C)] structs and unsafe extern "C" declarations into $OUT_DIR/bindings.rs.

3. The -sys Crate Architecture Convention

In the Cargo ecosystem, native library bindings are split into a two-crate pair:

Crate Role Naming Convention Responsibilities
Low-Level FFI Crate <lib>-sys (e.g., libz-sys, libsqlite3-sys) Declares links = "z" in Cargo.toml (ensuring at most one crate in the dependency graph links the native C library), runs build.rs, and exports raw unsafe extern "C" declarations.
High-Level Safe Wrapper <lib> (e.g., flate2, rusqlite) Depends on <lib>-sys and wraps the raw C pointers and error codes into safe, idiomatic RAII Rust types (Drop, Result, lifetimes).