R
RUSTY YELLOW PAGES

externKeyword


Explanation

extern marks a boundary between Rust and a foreign calling convention — almost always C, or something that presents a C-compatible ABI. It has two main forms, plus an ABI-string vocabulary shared by both. (A third, mostly-historical form, extern crate, declares a dependency on another Rust crate rather than a foreign-language boundary and is covered on its own page, extern crate — this page covers only the FFI/ABI use of plain extern.)

extern "C" { ... } — a block declaring foreign items. This form tells the compiler the signature of a function or static that lives in a foreign library, without giving it a body — the compiler trusts the declaration completely and generates calls against it. As of the 2024 edition, this block must itself be written unsafe extern "C" { ... }: nothing about a foreign declaration can be checked by the compiler, so the block is an explicit assertion that its contents are trusted. Under earlier editions the same block was written without the leading unsafe; edition 2024 code that omits it is a compile error, and cargo fix handles the mechanical migration.

extern "C" fn — a Rust function using the C calling convention. Applying extern "C" to a Rust function definition (rather than a block) selects the C ABI for that function instead of Rust's own, unstable internal calling convention, which is what makes the function callable from foreign code at all. This is almost always paired with an attribute that keeps the function's symbol name intact in the compiled binary — see the linkage-attribute page for #[unsafe(no_mangle)] itself, which this page does not re-explain.

Other ABI strings. "C" is by far the most common string after extern and is also the default if the string is omitted entirely (extern fn means extern "C" fn). "system" behaves like "C" on most platforms but adapts to whatever convention the platform's native API actually uses — notably stdcall for 32-bit Windows API calls — so code targeting the Windows API should write extern "system" rather than hard-coding "C". "Rust" is the implicit calling convention every ordinary Rust function already uses when no extern is written at all; it has no stable, documented layout across compiler versions and exists in the ABI-string vocabulary mainly for completeness and for function pointer types that want to be explicit about it.

The safe weak keyword, inside unsafe extern blocks. Since the whole unsafe extern "C" { ... } block is trusted wholesale, every item declared inside it is, by default, unsafe to call or access — a caller must still write unsafe { ... } at each use, exactly as with any other unsafe fn or mutable static. The safe keyword, written before an individual item's declaration inside the block (safe fn tick_count() -> u32; or safe static DEVICE_ID: u32;), overrides this default for that one item: it asserts the item is actually safe to call or read with no surrounding unsafe block needed at the call site. This is deliberately per-item rather than block-wide — a header full of foreign declarations usually mixes genuinely pure, side-effect-free functions (reading a hardware ID, returning a constant) with ones that mutate shared state or have pointer-validity preconditions, and safe lets the binding's author be selective about which is which instead of forcing every caller to write unsafe even for the harmless ones. safe is a weak keyword: it has no special meaning outside this exact position, so it remains usable as an ordinary identifier elsewhere.

See FFI for why this boundary exists at all and how it fits into a real crate's design.

Usage examples

Declaring a foreign function's signature

unsafe extern "C" {
    fn abs(input: i32) -> i32; // <- `extern "C"`: declares a foreign function's signature
}

Crossing an FFI boundary

A media-processing crate needs both directions at once: calling a vendor compression library to shrink a buffer, and exposing a Rust callback that the same library invokes to report progress as it works.

unsafe extern "C" {
    fn compress_buffer(data: *const u8, len: usize, out_len: *mut usize) -> *mut u8; // <- `extern "C"`: calling INTO the foreign library
}

pub fn compress(input: &[u8]) -> Vec<u8> {
    let mut out_len: usize = 0;
    unsafe {
        // SAFETY: `input` is a valid slice for its full length; `out_len`
        // is a valid, aligned `usize` the library writes its result into.
        let ptr = compress_buffer(input.as_ptr(), input.len(), &mut out_len);
        let bytes = std::slice::from_raw_parts(ptr, out_len).to_vec();
        libc_free(ptr);
        bytes
    }
}

unsafe extern "C" {
    fn libc_free(ptr: *mut u8);
}

#[unsafe(no_mangle)] // <- keeps the symbol name stable so the C library can call it by name
pub extern "C" fn on_compress_progress(percent: u32) {
    // <- `extern "C"`: crossing FROM C back into Rust, called by the library itself
    eprintln!("compressing: {percent}%");
}

extern "C" on both the declared foreign function and the exported callback selects the same calling convention on each side of the boundary, which is what lets the two languages agree on how arguments and the return value are passed at all — the Rustonomicon's FFI chapter covers calling out and being called back as the two symmetric halves of one boundary.

Designing a public API

A binding crate around a legacy timer/watchdog HAL header wants to let callers read the current tick count without writing unsafe themselves, while still requiring unsafe for the one function that actually resets hardware state.

unsafe extern "C" {
    safe fn hal_tick_count() -> u32; // <- `safe`: pure, side-effect-free — no `unsafe` needed to call it
    fn hal_watchdog_reset(); // <- no `safe`: mutates hardware state, callers must still write `unsafe`
}

pub fn uptime_ticks() -> u32 {
    hal_tick_count() // <- callable directly: `safe` opted this one item out of the block's default
}

pub fn feed_watchdog() {
    unsafe {
        // SAFETY: resetting the watchdog is safe to call from any
        // context; documented by the vendor as reentrant.
        hal_watchdog_reset(); // <- still requires `unsafe`: this item wasn't marked `safe`
    }
}

Marking only the genuinely side-effect-free declaration safe lets a wrapper crate be precise about which parts of a trusted C header are actually safe to expose directly, instead of either forcing unsafe on every foreign call or (worse) wrapping the whole header in a blanket safe API without actually checking each item — the per-item safe keyword is exactly the edition-2024 mechanism the Rust Edition Guide introduced alongside unsafe extern blocks for this purpose.

Explanation

Embedded support: Full

extern is, if anything, more central in embedded Rust than in hosted Rust, because binding against a chip vendor's C SDK is one of the most common ways an embedded crate reaches hardware functionality that doesn't yet have (or will never get) a pure-Rust equivalent. unsafe extern "C" { ... } blocks are the standard way to declare the signatures of a vendor HAL's C functions — an ST-style HAL_GPIO_Init call, a chip's C peripheral library, or a C RTOS's task-creation API — so Rust code can call directly into a .a/.o compiled from that vendor's C sources, typically pulled in via a build.rs script (using the cc crate) rather than the system's dynamic linker finding a shared library, since a bare-metal target has no shared-library loader at all. The reverse direction matters just as much: an interrupt vector table is, at its core, a table of extern "C" function pointers the linker wires directly into the vector-table section — #[interrupt] fn TIM2() { ... } in a cortex-m-rt-based crate ultimately relies on the function having a stable, C-compatible calling convention and a stable symbol name so the linker script can place its address at the correct vector-table slot.

Usage examples

Binding a vendor C HAL function via `extern "C"`

unsafe extern "C" {
    fn HAL_GPIO_WritePin(gpio_port: *mut core::ffi::c_void, pin: u16, state: u8); // <- `extern "C"`: declares the vendor C HAL's signature
}

pub fn set_pin_high(port: *mut core::ffi::c_void, pin: u16) {
    unsafe {
        // SAFETY: `port` is a valid GPIO port base address configured by the vendor's init code.
        HAL_GPIO_WritePin(port, pin, 1);
    }
}

Exposing a Rust interrupt handler with C linkage for the vector table

#[unsafe(no_mangle)]
pub extern "C" fn TIM2_IRQHandler() {
    // <- `extern "C"`: the linker's vector-table entry calls this by a fixed symbol name and calling convention
    unsafe { clear_tim2_update_flag() };
}

unsafe extern "C" {
    fn clear_tim2_update_flag();
}