Variadic functions
You have been calling a variadic function since the very first lesson. fmt.Println accepts any number of arguments: one, five, or zero. That flexibility is not magic, it is a feature of Go's function declaration syntax called variadic parameters.
Declaring one
Put ... before the type of the last parameter:
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
The ...int says "zero or more int values, packed together at this position". Inside the function, nums is a slice (the same collection type you met briefly in the for-range lesson: an ordered sequence you can iterate with range). Slices get a full treatment in the next chapter; for now, you can see exactly what nums looks like inside the function by printing it:
func dumpArgs(nums ...int) {
fmt.Println(nums)
}
func main() {
dumpArgs(1, 2, 3) // [1 2 3]
dumpArgs(42) // [42]
dumpArgs() // []
}
The caller passes individual int values; inside the function those values arrive as one slice, printed in Go's default slice format (square brackets around space-separated elements). Zero arguments produces an empty slice, not a crash, which is why fmt.Println() with no arguments still compiles and just prints a bare newline.
With that shape in mind, sum works over nums using the range loop you already know. Call it with any number of arguments:
fmt.Println(sum(1, 2, 3)) // 6
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
fmt.Println(sum()) // 0
The variadic parameter must be last
A function can have many ordinary parameters before the variadic one, but at most one variadic parameter, and it has to be last:
func logf(prefix string, values ...int) { ... } // fine: prefix first, variadic last
func logf(values ...int, prefix string) { ... } // compile error: variadic must be last
The rule exists so the compiler can tell where the regular parameters end and the variable-length list begins.
Spreading a slice with ...
Sometimes you already have the values in a slice and want to pass them to a variadic function. You spread them with a trailing ... at the call site:
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(sum(numbers...)) // 15
The trailing ... tells Go to unpack the slice and pass each element as a separate argument. Without it, the compiler complains that you are passing a slice where individual int values are expected.
numbers := []int{1, 2, 3}
fmt.Println(sum(numbers)) // compile error: cannot use numbers (variable of type []int) as int value in argument to sum
The ... in the declaration (nums ...int) and the ... at the call site (numbers...) are two sides of the same mechanism: one packs incoming arguments into a slice, the other unpacks a slice back into arguments.
The two options are func f(xs ...int) and func f(xs []int). The right one depends on the shape of a typical call. Reach for variadic when callers most often pass values they wrote directly at the call site, like sum(1, 2, 3) or max(4, 1, 7, 3). Reach for a plain slice parameter when callers usually already have the values collected in a slice, like nums := []int{1, 2, 3} followed by f(nums). If both shapes are common, variadic is often the nicer default because slice-holding callers can still write f(nums...).
-
Write a variadic function called
maxthat takes any number ofintarguments and returns the largest one. You can assume at least one argument is always passed. -
Call it from
mainin three ways and print each result on its own line: -
max(4, 1, 7, 3)should print7 -
max(42)should print42 -
Declare a slice
nums := []int{8, 2, 10, 5}and callmax(nums...). It should print10.
7 42 10