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:
- Mark the function
pub extern "C" fnso it uses the platform's C calling convention. - Annotate it with
#[unsafe(no_mangle)](or#[unsafe(export_name = "...")]) sorustcpreserves the exact function name in the symbol table instead of mangling it with a crate/module hash. - Configure
[lib]inCargo.tomlwithcrate-type = ["cdylib"](for a dynamic library.so/.dylib/.dll) orcrate-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 |