On this page 40
Variadic Functions in Home
Variadic functions accept a variable number of arguments, enabling flexible APIs and utility functions. Home supports both type-safe variadic parameters and spread syntax.
Table of Contents
- Basic Syntax
- Variadic Parameters
- Spread Operator
- Type Safety
- Built-in Variadic Functions
- Generic Variadic Functions
- Best Practices
Basic Syntax
Defining Variadic Functions
// Variadic parameter with ...T syntax
fn sum(numbers: ...i32): i32 {
let total = 0
for num in numbers {
total += num
}
total
}
// Call with any number of arguments
let result = sum(1, 2, 3, 4, 5) // 15
let result2 = sum(10, 20) // 30
let result3 = sum() // 0
Mixed Parameters
// Regular parameters followed by variadic
fn format(template: string, ...args: any): string {
// Format string with arguments
template.format(args)
}
let msg = format("Hello, {}! You have {} messages", "Alice", 5)
Variadic Parameters
Syntax
// Type-safe variadic parameter
fn print_all(...items: string): void {
for item in items {
println(item)
}
}
// Generic variadic parameter
fn max<T: Ord>(...values: T): T {
let mut maximum = values[0]
for value in values[1..] {
if value > maximum {
maximum = value
}
}
maximum
}
Accessing Variadic Arguments
Variadic parameters are treated as slices within the function body:
fn describe(...items: string): void {
println("Received {} items", items.len())
for (i, item) in items.iter().enumerate() {
println(" [{}]: {}", i, item)
}
}
describe("apple", "banana", "cherry")
// Output:
// Received 3 items
// [0]: apple
// [1]: banana
// [2]: cherry
Spread Operator
Spreading Arrays
let numbers = [1, 2, 3]
let more = [4, 5, 6]
// Spread in function calls
let total = sum(...numbers) // 6
let all = sum(...numbers, ...more) // 21
let mixed = sum(0, ...numbers, 10) // 16
Spreading in Array Literals
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
// Create new array with spread
let combined = [...arr1, ...arr2] // [1, 2, 3, 4, 5, 6]
let extended = [0, ...arr1, 10] // [0, 1, 2, 3, 10]
Type Safety
Type Constraints
// All variadic arguments must be same type
fn add_numbers(...nums: i32): i32 {
nums.iter().sum()
}
add_numbers(1, 2, 3) // OK
add_numbers(1, 2.5, 3) // Error: expected i32, found f64
Generic Constraints
// Generic with trait bounds
fn print_all<T: Display>(...items: T): void {
for item in items {
println("{}", item)
}
}
print_all(1, 2, 3) // OK - i32 implements Display
print_all("a", "b", "c") // OK - string implements Display
Mixed Types with Any
// Accept any type (less safe)
fn log(...items: any): void {
for item in items {
println("{:?}", item)
}
}
log(1, "hello", true, 3.14) // OK - different types
Built-in Variadic Functions
println
// Print with automatic formatting
println("Hello, World!")
println("x =", x, "y =", y)
println("Values:", 1, 2, 3, 4, 5)
format
// String formatting
let msg = format("Hello, {}!", "World")
let info = format("{} + {} = {}", 2, 3, 5)
vec/array
// Create vector from values
let numbers = vec(1, 2, 3, 4, 5)
let mixed = vec("a", "b", "c")
max/min
// Find maximum
let maximum = max(1, 5, 3, 9, 2) // 9
let minimum = min(1, 5, 3, 9, 2) // 1
// Works with any Ord type
let max_str = max("apple", "zebra", "banana") // "zebra"
Generic Variadic Functions
Type Parameters
// Generic variadic function
fn first_of<T>(...items: T): Option<T> {
if items.len() > 0 {
Some(items[0])
} else {
None
}
}
let num = first_of(1, 2, 3) // Some(1)
let str = first_of("a", "b") // Some("a")
let none = first_of::<i32>() // None
Multiple Type Parameters
// Multiple generics with variadic
fn zip_with<T, U, R>(
func: fn(T, U): R,
ts: ...T,
us: ...U
): Vec<R> {
let mut results = vec![]
let len = min(ts.len(), us.len())
for i in 0..len {
results.push(func(ts[i], us[i]))
}
results
}
let sums = zip_with(
|a, b| a + b,
1, 2, 3,
10, 20, 30
) // [11, 22, 33]
Advanced Patterns
Variadic Macros
// Macro with variadic arguments
macro_rules! vec {
($($x:expr),_) => {
{
let mut temp_vec = Vec::new()
$(
temp_vec.push($x);
)_
temp_vec
}
};
}
let v = vec![1, 2, 3, 4, 5]
Builder Pattern
struct QueryBuilder {
conditions: Vec<string>,
}
impl QueryBuilder {
fn new(): QueryBuilder {
QueryBuilder { conditions: vec![] }
}
fn where_in(&mut self, field: string, ...values: any): &mut QueryBuilder {
let vals = values.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
self.conditions.push(format("{} IN ({})", field, vals))
self
}
}
let query = QueryBuilder::new()
.where_in("id", 1, 2, 3, 4, 5)
.build()
Recursive Variadic
// Process variadic arguments recursively
fn process_all<T>(...items: T): void
where
T: Display
{
if items.len() == 0 {
return
}
println("{}", items[0])
process_all(...items[1..])
}
Best Practices
1. Use Type Constraints
// Good - type safe
fn sum_numbers(...nums: i32): i32 {
nums.iter().sum()
}
// Avoid - too permissive
fn sum_anything(...items: any): any {
// Type safety lost
}
2. Provide Clear Documentation
/// Calculates the average of the given numbers.
///
/// # Arguments
/// * `numbers` - Variable number of numeric values
///
/// # Returns
/// The arithmetic mean, or 0 if no numbers provided
///
/// # Examples
/// ```
/// let avg = average(1, 2, 3, 4, 5) // 3
/// ```
fn average(...numbers: f64): f64 {
if numbers.len() == 0 {
return 0.0
}
numbers.iter().sum::<f64>() / numbers.len() as f64
}
3. Consider Minimum Arguments
// Require at least one argument
fn max<T: Ord>(first: T, ...rest: T): T {
let mut maximum = first
for value in rest {
if value > maximum {
maximum = value
}
}
maximum
}
// Compile-time error if called with no arguments
let m = max() // Error: missing required argument
4. Use Spread for Readability
// Good - clear intent
let all_numbers = [...first_batch, ...second_batch, ...third_batch]
// Avoid - manual concatenation
let all_numbers = first_batch.concat(second_batch).concat(third_batch)
5. Limit Variadic Complexity
// Good - simple variadic
fn concat(...strings: string): string {
strings.join("")
}
// Avoid - too complex
fn complex(...items: any): Result<any, Error> {
// Too much type checking and branching
}
Limitations
Current Limitations
- Single variadic parameter - Only one variadic parameter per function
- Must be last - Variadic parameter must be the last parameter
- No named variadic - Cannot use named arguments with variadic parameters
// OK
fn func(a: i32, b: string, ...rest: i32): void { }
// Error - variadic not last
fn func(...rest: i32, a: i32): void { }
// Error - multiple variadic
fn func(...nums: i32, ...strs: string): void { }
Examples
Logging Utility
enum LogLevel {
Debug,
Info,
Warn,
Error,
}
fn log(level: LogLevel, ...messages: any): void {
let prefix = match level {
LogLevel::Debug => "[DEBUG]",
LogLevel::Info => "[INFO]",
LogLevel::Warn => "[WARN]",
LogLevel::Error => "[ERROR]",
}
print("{} ", prefix)
for (i, msg) in messages.iter().enumerate() {
if i > 0 {
print(" ")
}
print("{:?}", msg)
}
println("")
}
log(LogLevel::Info, "Server started on port", 8080)
log(LogLevel::Error, "Failed to connect:", error_msg)
SQL Query Builder
fn select(...columns: string): QueryBuilder {
QueryBuilder::new().select(columns)
}
fn where_clause(field: string, op: string, ...values: any): Condition {
Condition::new(field, op, values)
}
let query = select("id", "name", "email")
.from("users")
.where_clause("age", ">", 18)
.where_clause("status", "IN", "active", "pending")
.build()
Test Assertions
fn assert_all_equal<T: PartialEq + Debug>(...values: T): void {
if values.len() < 2 {
return
}
let first = values[0]
for (i, value) in values[1..].iter().enumerate() {
if value != first {
panic!(
"Assertion failed: values[0] != values[{}]\n left: {:?}\n right: {:?}",
i + 1, first, value
)
}
}
}
assert_all_equal(1, 1, 1, 1) // OK
assert_all_equal(1, 1, 2, 1) // Panics