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-configcrate): Queries the system'spkg-configdatabase and automatically prints the requiredcargo::rustc-link-searchandcargo::rustc-link-liblines. - Bundled C/C++ compilation (
cccrate): Compiles.cor.cppfiles shipped inside your crate's source tree into a static archive in$OUT_DIRand links it automatically. - Automated binding generation (
bindgencrate): Parses a.hC header file and generates the exact Rust#[repr(C)]structs andunsafe 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). |