Foreign Function Interface (FFI)

Calling Rust Functions from C

To expose a Rust function so C (or any C-ABI host language) can call it, you combine three requirements:

  1. Mark the function pub extern "C" fn so it uses the platform's C calling convention.
  2. Annotate it with #[unsafe(no_mangle)] (or #[unsafe(export_name = "...")]) so rustc preserves the exact function name in the symbol table instead of mangling it with a crate/module hash.
  3. Configure [lib] in Cargo.toml with crate-type = ["cdylib"] (for a dynamic library .so/.dylib/.dll) or crate-type = ["staticlib"] (for a static archive .a/.lib).
Rust Source (`src/lib.rs`):
  #[unsafe(no_mangle)]
  pub extern "C" fn checksum_u32(ptr: *const u8, len: usize) -> u32
                         |
                         v  `cargo build --release` (crate-type = ["cdylib", "staticlib"])
C Header (`my_lib.h`):
  uint32_t checksum_u32(const uint8_t *ptr, size_t len);

Why #[unsafe(no_mangle)] Requires unsafe(...) in Rust 2024

Normally, rustc mangles every function name (e.g., _ZN6my_lib12checksum_u3217h8a9b...E) so two crates in the same binary can both define a function named init without colliding. Disabling name mangling exports a global symbol into the process's flat C namespace. If two libraries export an unmangled symbol with the same name and incompatible signatures, the linker may merge them and cause undefined behavior. In Rust 2024 edition, you write #[unsafe(no_mangle)] (or #[unsafe(export_name = "custom_c_name")]) to acknowledge this global symbol obligation.

/// Computes an Adler-32 checksum over a byte buffer passed from C.
///
/// Returns `0` if `ptr` is null.
///
/// # Safety
///
/// If `ptr` is non-null, it must point to at least `len` initialized, contiguous
/// bytes that remain valid and unmutated for the duration of this call.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn rust_adler32(ptr: *const u8, len: usize) -> u32 {
    if ptr.is_null() {
        return 0;
    }
    // SAFETY: Checked `ptr` is non-null; caller guarantees `ptr` is valid for `len` bytes.
    // Note: `slice::from_raw_parts` requires non-null even when `len == 0`!
    let bytes = unsafe { std::slice::from_raw_parts(ptr, len) };

    let mut a: u32 = 1;
    let mut b: u32 = 0;
    for &byte in bytes {
        a = (a + u32::from(byte)) % 65_521;
        b = (b + a) % 65_521;
    }
    (b << 16) | a
}

fn main() {
    let payload = b"Wikipedia";
    // SAFETY: `payload` is a valid, initialized byte slice.
    let sum = unsafe { rust_adler32(payload.as_ptr(), payload.len()) };
    assert_eq!(sum, 0x11E6_0398);
    println!("Adler-32 checksum of 'Wikipedia': {sum:#010x}");
}
Adler-32 checksum of 'Wikipedia': 0x11e60398

Corresponding C Header and C Caller

C code calls rust_adler32 using a standard C header declaration—which can be written by hand or generated automatically from your Rust code using the cbindgen tool (cargo install cbindgen):

/* File: my_rust_lib.h */
#include <stdint.h>
#include <stddef.h>

#ifdef __cplusplus
extern "C" {
#endif

uint32_t rust_adler32(const uint8_t *ptr, size_t len);

#ifdef __cplusplus
}
#endif
Rust Export Item Equivalent C Header Declaration
#[unsafe(no_mangle)] pub extern "C" fn init() -> i32 int32_t init(void);
ptr: *const u8, len: usize const uint8_t *ptr, size_t len
out: *mut u64 uint64_t *out