Recommended Free Tools
To call a C variadic function from Rust, declare it with the matching ABI and a final ..., then call it in an unsafe block only when the fixed and variadic arguments meet the C API’s contract. To expose a variadic function implemented in Rust, define an unsafe extern "C" function and read its arguments through Rust’s VaList API. Neither unsafe nor the C ABI checks the number or types of variadic arguments for you.
Call a variadic C function from Rust
Mirror the declaration in the platform’s C header: preserve its fixed parameters, return type, symbol and calling convention, then put ... last. An explicit unsafe extern "C" block makes the foreign boundary clear:
As an Amazon Associate I earn from qualifying purchases.
use core::ffi::{c_char, c_int};
unsafe extern "C" {
unsafe fn printf(format: *const c_char, ...) -> c_int;
}
// SAFETY: the format is NUL-terminated and %d matches the c_int argument.
unsafe {
printf(c"value=%dn".as_ptr(), 42 as c_int);
}
The example’s c"..." literal requires a Rust version that supports C string literals. With another version, provide a valid NUL-terminated string by an appropriate means. In real bindings, confirm that the declaration matches the target’s header and linked symbol; a plausible-looking Rust signature is not enough.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesIn Rust 2024, extern blocks must be marked unsafe. Marking the imported function itself unsafe also makes the call-site responsibility explicit. The unsafe block does not validate the declaration, format string, or arguments at runtime—it records that the programmer must uphold their contract. Rust’s Reference explains the extern block rules.
#1 Best Overall
Match the variadic contract exactly
The ellipsis does not give Rust type information about the tail. The caller and C function must agree on how many arguments are present and on their ABI types after C’s default argument promotions. For example, a printf format string must describe the actual arguments; passing an incompatible value or omitting an argument the function reads can cause undefined behavior.
C promotes integer arguments narrower than int to int, and floating-point arguments narrower than double to double. The callee interprets the promoted type, not necessarily the source expression’s narrow type. Consult the relevant C API documentation and platform declarations for its exact rules.
Rank #2
A variadic function may still require one or more arguments. For example, printf requires its format argument; an empty variadic tail does not make the fixed argument optional.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Define a Rust variadic function for C callers
A Rust definition that accepts C variadic arguments must be unsafe, use extern "C" or extern "C-unwind", and place ... last. Rust supplies the variadic parameter as a VaList; the compiler initializes it as C’s va_start would. The function Reference documents the syntax and target restrictions in its variadic functions section.
Rank #3
use core::ffi::c_int;
unsafe extern "C" fn sum_two(mut args: ...) -> c_int {
// SAFETY: callers must provide at least two c_int-compatible arguments.
unsafe {
let first = args.next_arg::<c_int>();
let second = args.next_arg::<c_int>();
first + second
}
}
This example is safe to use only under its stated calling contract: every caller must supply at least two arguments whose promoted ABI types are compatible with c_int. A production API should document that requirement for C callers, and should use a count, sentinel, or other defined rule when the number of arguments varies.
Read only arguments that exist and have compatible types
VaList::next_arg::<T>() is unsafe. Call it only when the caller has supplied another argument and the requested T is compatible with the actual promoted argument type. Rust documents compatibility for the same type, certain integer types of equal size when the value is representable in both, and compatible pointer types. These conditions are not inferred from a format string or checked by the compiler.
The standard library API is documented at VaList. Choose T based on the C calling contract after default promotions. Reading past the available arguments, or reading an incompatible type, is not made valid by wrapping the call in unsafe.
Keep VaList traversal within the call
A VaList is tied to the variadic call’s lifetime. Do not return it, store it for later, or otherwise let a borrowed list escape the function scope. If a second independent traversal is needed, clone the list with the documented API; this corresponds to C’s va_copy. Dropping the list corresponds to va_end. Use these APIs rather than defining a Rust representation of the platform’s va_list.
Quick Recap
Check ABI, edition, and target support
- ABI: use the ABI specified by the C interface. Rust supports variadic declarations with several ABIs, but a definition accepting C callers must use
extern "C"orextern "C-unwind". The ABI is part of the contract, not a substitute for matching argument types. - Edition: Rust 2024 requires
unsafeon extern blocks. Check the edition and compiler version used by the project, especially when adapting older examples. - Target: Rust supports defining variadic functions only on documented target architectures. Check the current Function Reference for the target you build for; support on one architecture does not imply support on another.
- Library API: verify availability and stability of the
VaListAPIs for the project’s Rust version and target. Do not assume an example compiles unchanged across toolchains.
Safety checklist
- Does the Rust declaration match the C header’s fixed parameters, return type, ABI, and linked symbol?
- Does each caller supply the required number of variadic arguments?
- Do caller and callee agree on each argument’s ABI type after C default promotions?
- Does each
next_argread an argument that exists and whose type is compatible with the requested type? - Are the count or stopping rule, format or sentinel contract, and any assumptions documented for C callers?
- Are target support, edition requirements, and library APIs verified for the actual toolchain?
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




