14. Variadic functions
You've been calling one since lesson one. fmt.Println("a", "b", "c") takes
as many arguments as you feel like giving it. Functions that accept a
variable number of arguments are called variadic, and you can write your
own.
Writing one
Put ... before the type of the last parameter:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3))
fmt.Println(sum(10, 20))
fmt.Println(sum())
}
Inside the function, nums is an ordinary slice of int — you range
over it exactly as you would any slice. Go builds that slice for you from
whatever arguments the caller passed.
Called with no arguments at all, nums is an empty slice and sum()
returns 0. No special case needed.
Mixing fixed and variadic parameters
The variadic parameter must come last, but it can follow normal ones:
package main
import "fmt"
func join(sep string, parts ...string) string {
out := ""
for i, p := range parts {
if i > 0 {
out += sep
}
out += p
}
return out
}
func main() {
fmt.Println(join("-", "go", "is", "fun"))
fmt.Println(join(", ", "solo"))
}
Only one variadic parameter is allowed, and it has to be at the end — the compiler needs to know where the fixed arguments stop.
Spreading a slice with ...
What if you already have a slice and want to pass it to a variadic
function? Add ... after the argument:
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
values := []int{4, 8, 15, 16}
fmt.Println(sum(values...))
fmt.Println(sum(values[:2]...))
}
sum(values...) means "use this slice as the argument list". Without the
dots you'd get a type error — Go would think you're passing one []int
where it expected ints.
So ... appears in two places with mirrored meanings: in a declaration
it packs arguments into a slice; at a call site it unpacks a slice into
arguments.
The slice is shared, not copied
A subtle detail worth knowing before it bites you: when you spread a slice, the function receives the same underlying array, not a copy. Modifying it inside the function modifies the caller's slice:
package main
import "fmt"
func zeroFirst(nums ...int) {
if len(nums) > 0 {
nums[0] = 0
}
}
func main() {
values := []int{7, 8, 9}
zeroFirst(values...)
fmt.Println(values)
}
values is now [0 8 9]. This is slice behaviour, not variadic magic —
the Collections module explains exactly why. Passing arguments individually
(zeroFirst(7, 8, 9)) builds a fresh slice, so there's nothing of yours to
modify.
Where you already use it
The standard library is full of variadic functions:
fmt.Println(a ...any)— any number of values, of any typefmt.Printf(format string, a ...any)— fixed format, then the valuesappend(slice, elems ...T)— the most-used one of allstrings.Joinis not variadic, and takes a slice — because it wants precisely one list
The any in those signatures is Go's name for "a value of any type at all".
You'll meet it properly in the Interfaces module.
Your turn
Write longest, which takes any number of strings and returns the longest
one (the first, if several tie). Return "" for no arguments. Print the
result so the program outputs exactly:
elephant
package main
import "fmt"
// write longest here
func main() {
fmt.Println(longest("cat", "elephant", "horse"))
}
package main
import "fmt"
func longest(words ...string) string {
best := ""
for _, w := range words {
if len(w) > len(best) {
best = w
}
}
return best
}
func main() {
fmt.Println(longest("cat", "elephant", "horse"))
}
Next: functions stop being things you call and start being things you can store, pass around and return.