What is a variadic function?
As we have seen in a functions
lesson, a function is a piece of code dedicated to perform a particular job. A function takes one or many arguments and may return one or many values.
Variadic functions are also functions but they can take an infinite or variable number of arguments. Sounds stupid but we have seen this in slices
lesson when append
function accepted a variable number of arguments.
func f(elem ...Type)
A typical syntax of a variadic function looks like above. ...
operator called as pack operator instructs Go to store all arguments of type Type
in elem
parameter. With this syntax, Go creates elem
variable of the type []Type
which is a slice. Hence, all arguments passed to this function is stored in a elem
slice.
Let’s take an example of append
function.
append([]Type, args, arg2, argsN)
append
function expects the first argument to be a slice
of type Type
, while there can be a variable number of arguments after that. If we have a slice s2
that we want to append to a slice s1
, how that will work?
As from append
function syntax, we can’t pass another slice as an argument, it has to be one or many arguments of type Type
. Hence, instead, we will use the unpack operator ...
to unpack slice into the series of arguments (which is acceptable by append
function).
append(s1, s2...)
💡
...
signifies bothpack
andunpack
operator but if three dots are in the tail position, it will unpack a slice.
Here s1
and s2
are two slices of the same type. Usually, we know function parameters and how many arguments a function can accept. Then how append
function knows how many parameters has passed to it?
If you look at the signature append
function,
func append(slice []Type, elems ...Type) []Type
You will see elems ...Type
which means pack all incoming arguments into elems
slice after the first argument.
💡 One important thing to notice is that only the last argument of a function is allowed to be variadic.
So the first argument to append
function will be a slice because it demands a slice but later arguments will be packed into one argument elems
. I hope that makes it clear and now let’s look at creating your own variadic
function.
How to create a variadic function?
As discussed earlier, variadic function nothing but a function that accepts a variable number of arguments. To make a function accept a variable number of arguments, we need to use pack
operator ...Type
.
💡 Unpack operator ends with
...
likeslice...
while pack operator starts with...
like...Type
.
Let’s write getMultiples
function whose first argument is factor
of type int
which is a factor of multiplication and later variable arguments (hence variadic arguments) of type int
are packed into the slice args
.
In this function, we are creating an empty slice using make
function with length equal to the length of args
which is a slice. Using for range
, we are multiplying factor
with elements of args
and saving them in multiples
. Later, we return the slice multiples
.
func getMultiples(factor int, args ...int) []int {
multiples := make([]int, len(args))
for index, val := range args {
multiples[index] = val * factor
}
return multiples
}
This is as simple as it can get. We can implement this function inside main
function like
func main() {
s := []int{10, 20, 30}
mult1 := getMultiples(2, s...)
mult2 := getMultiples(3, 1, 2, 3, 4)
fmt.Println(mult1)
fmt.Println(mult2)
}
package main
import "fmt"
func getMultiples(factor int, args ...int) []int {
multiples := make([]int, len(args))
for index, val := range args {
multiples[index] = val * factor
}
return multiples
}
func main() {
s := []int{10, 20, 30}
// get multiples of 2, pass parameters from slice using `unpack` operator
mult1 := getMultiples(2, s...)
// get multiples of 3
mult2 := getMultiples(3, 1, 2, 3, 4)
fmt.Println(mult1)
fmt.Println(mult2)
}
// [20 40 60]
// [3 6 9 12]
💡 What will happen if in above example, you pass slice
s
directly togetMultiples
function as second argument. Obviously, compiler will complaincannot use s (type []int) as type int in argument to getMultiples
because slice is type of[]int
andgetMultiples
expects parameter(s) ofint
.
How slice
is passed to a variadic function?
A slice is a reference to an array, what happens when you pass a slice to a variadic function using unpack
operator. Does Go creates a new slice args
or keeps the same slice s?
Since, we don’t have any tool to compare, args == s
, we need to mutate args
slice itself to check if the original slice s
mutated.
package main
import "fmt"
func getMultiples(factor int, args ...int) []int {
for index, val := range args {
args[index] = val * factor
}
return args
}
func main() {
s := []int{10, 20, 30}
mult1 := getMultiples(2, s...)
fmt.Println(s, mult1)
}
// [20 40 60] [20 40 60]
In the above program, we have modified getMultiples
variadic function slightly and instead of creating a new slice, we assigned multiplication values to args
itself replacing incoming elements with multiplied elements.
From the above result, we can see values of slice s
changed. This means, Go in case of slice
when passed to a variadic function using unpack
operator, will use underneath array to build new slice. So, be careful.