8.15 Variadic Functions and Macros
C allows variadic functions – functions that can accept a variable number of arguments, like printf
or scanf
, using the ...
syntax and stdarg.h
macros.
// C Example (for comparison)
#include <stdio.h>
#include <stdarg.h>
// 'count' indicates how many numbers follow.
void print_ints(int count, ...) {
va_list args;
va_start(args, count); // Initialize args to retrieve arguments after 'count'.
printf("Printing %d integers: ", count);
for (int i = 0; i < count; i++) {
int value = va_arg(args, int); // Retrieve next argument as an int.
printf("%d ", value);
}
va_end(args); // Clean up.
printf("\n");
}
int main() {
print_ints(3, 10, 20, 30); // Call with 3 variable arguments.
print_ints(5, 1, 2, 3, 4, 5); // Call with 5 variable arguments.
return 0;
}
Variadic functions in C are powerful but lack type safety for the variable arguments, which can lead to runtime errors if the types or number of arguments retrieved using va_arg
don’t match what was passed.
Rust does not support defining C-style variadic functions directly in safe code. You can call C variadic functions from Rust using FFI (Foreign Function Interface) within an unsafe
block, but you cannot define your own safe variadic functions using ...
.
The idiomatic way to achieve similar functionality in Rust (accepting a varying number of arguments) is through macros. Macros operate at compile time, expanding code based on the arguments provided. They are type-safe and more flexible than C variadics.
// Define a macro named 'print_all' macro_rules! print_all { // Match one or more expressions separated by commas ( $( $x:expr ),+ ) => { // Repeat the following code block for each matched expression '$x' $( print!("{} ", $x); // Print each expression )+ println!(); // Print a newline at the end }; } fn main() { print_all!(1, "hello", true, 3.14); // Call the macro with different types print_all!(100, 200); // Call with just integers }
Macros like println!
itself are prime examples of this pattern. They provide a type-safe, compile-time mechanism for handling variable arguments, which aligns better with Rust’s safety goals than C-style variadics. Macros are a more advanced topic covered later in the book.