Foreign Function Interface (FFI)

C-Compatible Types

In the C language specification, fundamental integer types such as int, long, and char do not have fixed bit widths across all operating systems and CPU architectures. Rust's core::ffi module (re-exported as std::ffi) provides platform-exact type aliases (c_int, c_long, c_char, c_void) that automatically resolve to the exact width and signedness of the target platform's C compiler.

Why `c_long` != `i64` Everywhere (64-bit Data Models):

  Platform                 C `int` (`c_int`)   C `long` (`c_long`)   Pointer (`*mut c_void`)
  ------------------------------------------------------------------------------------------
  64-bit Linux / macOS     32-bit (`i32`)      64-bit (`i64`)        64-bit (LP64 Model)
  64-bit Windows (MSVC)    32-bit (`i32`)      32-bit (`i32`)!       64-bit (LLP64 Model)

Hardcoding i64 in Rust for a C function parameter declared as unsigned long or long works on 64-bit Linux and macOS, but silently corrupts the stack/registers on 64-bit Windows where C's long is 32 bits (i32)! Always use core::ffi::c_long and core::ffi::c_ulong when matching C declarations that use long.

Complete Mapping Between C Types and Rust Types

C Type (<stdint.h> / Standard C) Rust Type (core::ffi / Primitive) Notes
int8_t / int16_t / int32_t / int64_t i8 / i16 / i32 / i64 Exact fixed-width signed integers; identical across all platforms.
uint8_t / uint16_t / uint32_t / uint64_t u8 / u16 / u32 / u64 Exact fixed-width unsigned integers.
size_t / uintptr_t usize Pointer-sized unsigned integer (c_size_t).
ssize_t / ptrdiff_t / intptr_t isize Pointer-sized signed integer (c_ssize_t / c_ptrdiff_t).
char core::ffi::c_char i8 on x86_64/macOS, u8 on Linux ARM64 (aarch64)! Never hardcode i8 or u8 for char*.
signed char / unsigned char c_schar (i8) / c_uchar (u8) Explicitly signed or unsigned byte.
short / unsigned short c_short / c_ushort Typically i16 / u16.
int / unsigned int c_int / c_uint Typically i32 / u32 on 32-bit and 64-bit targets.
long / unsigned long c_long / c_ulong i64 on 64-bit Unix (LP64); i32 on 64-bit Windows (LLP64)!
long long / unsigned long long c_longlong / c_ulonglong At least 64 bits (i64 / u64).
float / double c_float (f32) / c_double (f64) IEEE 754 single and double precision floats.
bool (_Bool in C99+) bool 1-byte boolean (0 = false, 1 = true; any other bit pattern is UB in Rust!).
void* / const void* *mut c_void / *const c_void Opaque untyped pointer (core::ffi::c_void).

Inspecting Platform core::ffi Type Sizes at Runtime

use core::ffi::{c_char, c_double, c_int, c_long, c_ulong, c_void};
use std::mem::size_of;

fn main() {
    println!("size_of::<c_char>()   = {} byte(s)", size_of::<c_char>());
    println!("size_of::<c_int>()    = {} byte(s)", size_of::<c_int>());
    println!("size_of::<c_long>()   = {} byte(s)", size_of::<c_long>());
    println!("size_of::<c_ulong>()  = {} byte(s)", size_of::<c_ulong>());
    println!("size_of::<c_double>() = {} byte(s)", size_of::<c_double>());
    println!("size_of::<*mut c_void>() = {} byte(s)", size_of::<*mut c_void>());
}
size_of::<c_char>()   = 1 byte(s)
size_of::<c_int>()    = 4 byte(s)
size_of::<c_long>()   = 8 byte(s)
size_of::<c_ulong>()  = 8 byte(s)
size_of::<c_double>() = 8 byte(s)
size_of::<*mut c_void>() = 8 byte(s)

Why c_void Exists Instead of ()

For a C function that returns void (void reset_state(void);), omit the return type in Rust (returning ()). However, for a C pointer to void (void* or const void*), you must use *mut c_void or *const c_void—never *mut ()—because () is a zero-sized type (ZST) in Rust whose pointer operations (offset) are no-ops, whereas c_void is an opaque FFI type recognized by the improper_ctypes lint.