Variadic functions
fmt.Println accepts one value, three or none, while a function with the parameters a, b int always requires exactly two arguments. A sum or a maximum is also handy to compute for any number of values.
A function that accepts any number of arguments of one type is called a variadic function. Its last parameter has three dots before the type:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, v := range nums {
total += v
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3), sum(5), sum()) // 6 5 0
}Picture a left-luggage office that gives your bags a row of lockers of the right length: two bags, two lockers; five bags, five. Go does the same. It puts the call's arguments into the lockers of a new row and hands the function a window onto that row, that is, a slice.
The parameter is a slice
Inside the function the parameter nums ...int is an ordinary []int slice. The call's arguments go into it in order, and len(nums) returns their count. Let's walk through the three calls in the example:
sum(1, 2, 3):numsholds[1 2 3], the loop adds the three numbers, and the call returns6.sum(5):numshas one element, and the call returns5.sum(): the slice is empty, therangeloop does not run at all, andtotalstays0.
Everything that works with any slice works with nums: indexes, len, range and append. This is where the left-luggage analogy ends: in a call with no arguments no row is built at all, and nums is nil.
fmt.Println works the same way: its single parameter accepts any number of values of any type. That is why the call fmt.Println() with no arguments is allowed and prints an empty line.
Ordinary parameters before ...
Only the last parameter may have the three dots. Ordinary parameters may come before it: func scale(k int, nums ...int) takes the first number into k and the rest into nums. Let's look at two calls:
scale(10, 1, 2, 3)passes10intokand the slice[1 2 3]intonums;scale()with no arguments does not compile:kis always required.
Two ways to call
A variadic function can be called in two ways.
Separate values
The call sum(1, 2, 3) lists the arguments separated by commas. Go builds a new row for them, and the function gets a window onto it. Each argument is copied into that row, so a write to nums does not change the caller's variables.
A ready slice with three dots
If the numbers are already in a slice, you pass them by putting three dots after the slice's name:
s := []int{4, 5, 6}
fmt.Println(sum(s...)) // 15The call sum(s...) adds 4, 5 and 6, and the program prints 15. s... does not copy the elements: Go builds no new row and hands the function your own window. The parameter nums gets the same slice s, so the write nums[0] = 0 inside the function would change s too.
Interviewers like to ask exactly this: does the caller see changes made through a variadic parameter? With s... it does.
This way has two rules:
- the slice may be empty or
nil: thennumsis empty too, andsumreturns0; - separate values cannot be added to a slice with three dots:
sum(1, s...)does not compile, whilescale(10, s...)compiles, because10goes intok.
Common mistake
A slice is passed without the three dots:
s := []int{4, 5, 6}
fmt.Println(sum(s)) // ✗cannot use s (variable of type []int) as int value in argument to sumEvery argument of the variadic function must have the type int, and s is a slice. The compiler does not spread a slice into elements on its own. Write sum(s...).
Key points
- Inside the function, the parameter
nums ...intis an ordinary[]intslice; only the last parameter takes three dots. - A ready slice is passed as
sum(s...), and the function gets the same slice, with no copy of the elements. - Without the three dots, a slice in place of separate
intvalues does not compile.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.