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.