In this lesson, you will learn how to interface with C functions that accept a variable number of arguments—such as printf—by utilizing Rust’s c_variadic feature.
The Challenge of Variadic Functions
In C, variadic functions (like int printf(const char *format, ...);) use a mechanism defined in stdarg.h to process an unknown number of arguments. Because Rust’s type system enforces strict signature matching, we cannot natively call these functions without special handling. Since Rust 1.37, we have been able to declare and call these functions using the C ABI and the ... syntax.
Defining and Calling Variadic Functions
To interact with a C variadic function, you must first declare it within an extern "C" block. You mark the end of the fixed arguments with ..., representing the variadic portion.
extern "C" {
// Declaring a C variadic function
fn printf(format: *const i8, ...) -> i32;
}
fn main() {
let fmt = b"Value: %d\n\0".as_ptr() as *const i8;
unsafe {
// Calling the variadic function
printf(fmt, 42);
}
}
Safety Considerations
When calling these functions, you are operating entirely within an unsafe block. The compiler cannot verify the types or the number of arguments passed into the ... portion of the call. This places the burden of proof entirely on you:
- ABI Compatibility: You must ensure the types passed are compatible with what the C side expects. Passing a Rust
Stringor a complexstructinstead of a primitive type (likei32orf64) will likely lead to undefined behavior or stack corruption. - Promotion: C variadic functions perform "default argument promotions." For instance,
f32is typically promoted tof64. You must pass the promoted type, or your data will be misinterpreted. - Null Termination: As always with FFI, ensure your format strings are null-terminated (
\0), as C expects string pointers to terminate at the first null byte.
Because the compiler cannot check these constraints, consider wrapping these calls in a safe Rust function that handles the type conversion and safety requirements internally.
Key Takeaway: Use the ... syntax in extern "C" blocks to declare variadic functions, and always manually verify that the types you pass adhere to the C ABI’s default argument promotions.