Variadic
Some functions cannot say in advance how many arguments they take. A sum of some numbers, the largest of a few values — the work is the same whether there are two arguments or twenty. A variadic function accepts any count, zero included. You have been calling one since the first lesson: PrintLine takes a format string and then as many values as it has {} placeholders.
Collecting the rest
A last parameter written args: int32... collects every remaining argument into one slice:
func Sum(args: int32...) -> int32 {
var total: int32 = 0;
for value in args {
total += value;
}
return total;
}
Inside the function, args is an ordinary int32[..] — the Slice lesson's read-only view. It has a length, it can be indexed, and for walks it. So one function serves every count:
flowchart LR
c3["Sum(1, 2, 3)"] --> a3["args = view of 1, 2, 3<br/>length 3"]
c1["Sum(42)"] --> a1["args = view of 42<br/>length 1"]
c0["Sum()"] --> a0["args = empty view<br/>length 0"]Sum() is perfectly valid: the loop simply runs zero times and the total stays 0.
Ordinary parameters first
Ordinary parameters may come before the variadic one; only the last parameter can collect the rest:
func Largest(first: int32, args: int32...) -> int32 {
Here first is required, so Largest needs at least one argument — there is always a value to start from — and any more land in args.
Spreading a slice back out
Going the other way, args... at a call spreads a slice back out into separate arguments. That is how one variadic function hands everything it received to another:
func Average(args: int32...) -> int32 {
if args.length == 0 {
return 0;
}
return Sum(args...) / (args.length as int32);
}
args.length is a uint, so it is converted with as before dividing an int32 by it. An array can be spread too: it becomes a view of all its elements, just as it does when passed to a slice parameter:
let scores: int32[4] = [70, 85, 90, 95];
PrintLine("Average(scores...) {}", Average(scores...));
| Call | args inside Average |
|---|---|
Average(2, 4, 9) | 2, 4, 9 |
Average(scores...) | 70, 85, 90, 95 |
Average() | empty |
PrintLine is variadic
The PrintLine you have been calling with a format string is declared with a variadic parameter of its own — args: Display.... Display is not one type but a promise, "can be printed", which is why one call can mix an int, a float64 and a string. How such promises work is the subject of Display in the Interfaces part.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// Some functions cannot say in advance how many arguments they take. A last parameter written
// `args: int32...` collects every remaining argument into one slice, so a single function serves
// any count, zero included. Inside the function, `args` is an ordinary `int32[..]`: it has a
// length, it can be indexed, and `for` walks it.
//
// Going the other way, `args...` at a call spreads a slice back out into separate arguments,
// which is how one variadic function hands everything it received to another.
import Io::PrintLine;
func Sum(args: int32...) -> int32 {
var total: int32 = 0;
for value in args {
total += value;
}
return total;
}
// Ordinary parameters may come first; only the last one can collect the rest.
func Largest(first: int32, args: int32...) -> int32 {
var largest = first;
for value in args {
if value > largest {
largest = value;
}
}
return largest;
}
// `args...` passes what this function was given straight on to `Sum`.
func Average(args: int32...) -> int32 {
if args.length == 0 {
return 0;
}
return Sum(args...) / (args.length as int32);
}
func Main() -> int {
// The same function with three arguments, with one, and with none at all.
PrintLine("Sum(1, 2, 3) {}", Sum(1, 2, 3));
PrintLine("Sum(42) {}", Sum(42));
PrintLine("Sum() {}", Sum());
PrintLine("Largest(4, 9, 2) {}", Largest(4, 9, 2));
PrintLine("Largest(7) {}", Largest(7));
PrintLine("Average(2, 4, 9) {}", Average(2, 4, 9));
// An array can be spread too: it becomes a slice of all its elements, just as it does when
// passed to a slice parameter.
let scores: int32[4] = [70, 85, 90, 95];
PrintLine("Average(scores...) {}", Average(scores...));
return 0;
}
Run it
cd Examples/Sequences/Variadic
rux run
Sum(1, 2, 3) 6
Sum(42) 42
Sum() 0
Largest(4, 9, 2) 9
Largest(7) 7
Average(2, 4, 9) 5
Average(scores...) 85
Common mistakes
Sum(scores) fails with error: argument 1 to 'Sum' has type 'int32[4]', but variadic parameter 'args' requires 'int32' — each argument must be one int32. Write Sum(scores...).Sum(1, scores...) fails with error: spread argument to 'Sum' must be the only argument for variadic parameter 'args'. Either list the values one by one or spread a single slice.Largest() fails with error: call to 'Largest' expects at least 1 argument, but 0 were provided. Only the variadic parameter may receive nothing.Try it yourself
- Write
Smallest(first: int32, args: int32...) -> int32. - Write
CountAbove(limit: int32, args: int32...) -> int32and call it asCountAbove(80, scores...). - Print
Sum(scores[1..]...)— a spread works on any slice, not only a whole array. - Call
Sum(scores)without the...and read the error.
Learn more
- Variadic functions in the Rux Reference
- Indexing and iteration in the Reference — including spreading a slice into arguments
- Console —
PrintLineand its placeholders
5.5 Writable slice
Change an array's elements through a writable view, var int32[..], and see that a view's writability is separate from its binding's mutability.
5.7 Tuple
Group values without declaring a type, (int32, bool), reach the members with .0 and .1, and return several results from one function.