extern C
The extern "<ABI>" keyword specifies the Application Binary Interface (calling convention) of a function, function pointer type, or foreign declaration block. While ordinary Rust functions (fn) use the unstable default "Rust" calling convention (allowing rustc to reorder arguments or pass fields in arbitrary registers), extern "C" locks the function to the target platform's standard C calling convention.
Three Places Where `extern "C"` Appears in Rust:
1. Foreign Declaration Block (Importing C symbols):
unsafe extern "C" { fn getpid() -> i32; }
2. Exported Function Definition (Using C calling convention):
pub extern "C" fn my_callback(code: i32) -> i32 { code + 1 }
3. Function Pointer Type (Passing callbacks across FFI):
type CCallback = extern "C" fn(i32) -> i32;
Comparing ABI Strings Supported by Rust
| ABI String | Unwind Behavior on Panic | Description & Primary Use Case |
|---|---|---|
"Rust" (default fn) |
Unwinds Rust stack normally | Default ABI for all pure-Rust functions (fn()). Layout is not stabilized across compiler versions. |
"C" |
Aborts process if a panic escapes the function! | Standard C calling convention on the target platform (System V AMD64 ABI on Unix x86_64, AAPCS64 on ARM64, Microsoft x64 on Windows). |
"C-unwind" |
Allows unwinding across the ABI boundary | Identical register/stack layout to "C", but permits Rust panics or C++ exceptions (longjmp / DWARF unwind) to unwind cleanly across the boundary. |
"system" |
Aborts on uncaught panic | Platform OS API convention (stdcall on 32-bit x86 Windows; identical to "C" on all 64-bit platforms and Unix). |
"system-unwind" |
Allows unwinding | Unwind-capable counterpart to "system". |
Function Type Incompatibility Between fn and extern "C" fn
In Rust's type system, fn(i32) -> i32 and extern "C" fn(i32) -> i32 are distinct, incompatible types because the CPU instruction sequences used to pass arguments and clean up the stack may differ:
// ❌ does not compile
fn rust_abi_double(x: i32) -> i32 {
x * 2
}
fn register_c_handler(cb: extern "C" fn(i32) -> i32) -> i32 {
cb(21)
}
fn main() {
// Cannot pass a default `"Rust"` ABI function where `extern "C" fn` is expected:
let _ = register_c_handler(rust_abi_double);
}
error[E0308]: mismatched types
--> src/main.rs:12:32
|
12 | let _ = register_c_handler(rust_abi_double);
| ------------------ ^^^^^^^^^^^^^^^ expected `extern "C" fn`, found `fn`
| |
| arguments to this function are incorrect
|
= note: expected fn pointer `extern "C" fn(i32) -> i32`
found fn item `fn(i32) -> i32 {rust_abi_double}`
Marking the function extern "C" fn makes its type coerce to extern "C" fn(i32) -> i32:
extern "C" fn c_abi_double(x: i32) -> i32 {
x * 2
}
fn register_c_handler(cb: extern "C" fn(i32) -> i32) -> i32 {
cb(21)
}
fn main() {
println!("Invoked extern \"C\" fn pointer: {}", register_c_handler(c_abi_double));
}
Invoked extern "C\" fn pointer: 42
Implicit "C" ABI Shorthand
Writing bare extern fn or unsafe extern { ... } without an explicit string literal is syntactic sugar for extern "C" fn and unsafe extern "C" { ... }. However, explicit extern "C" is strongly preferred by Rust style conventions and Clippy (clippy::missing_abi) so the chosen ABI is immediately clear to readers.