Variadic Functions
Welcome to part 12 of our Golang tutorial series.
What is a variadic function?
Functions in general accept only a fixed number of arguments. A variadic function is a function that accepts a variable number of arguments. If the last parameter of a function definition is prefixed by ellipsis …, then the function can accept any number of arguments for that parameter.
Only the last parameter of a function can be variadic. We will learn why this is the case in the next section of this tutorial.
Syntax
1func hello(a int, b ...int) {
2}
In the above function, the parameter b
is variadic since it’s prefixed by ellipsis and it can accept any number of arguments. This function can be called by using the syntax.
1hello(1, 2) //passing one argument "2" to b
2hello(5, 6, 7, 8, 9) //passing arguments "6, 7, 8 and 9" to b
In the above piece of code, in line no. 1 we call hello
with one argument 2
for the parameter b
and we pass four arguments 6, 7, 8, 9
to b
in the next line.
It is also possible to pass zero arguments to a variadic function.
1hello(1)
In the above code, we call hello
with zero arguments for b
. This is perfectly fine.
By now I guess you would have understood why the variadic parameter should only be at the last.
Let’s try to make the first parameter of the hello
function variadic.
The syntax will look like
1func hello(b ...int, a int) {
2}
In the above function, it is not possible to pass arguments to the parameter a
because whatever argument we pass will be assigned to the first parameter b
since it’s variadic. Hence variadic parameters can only be present at the last in the function definition. The above function will fail to compile with error syntax error: cannot use ... with non-final parameter b
Examples and understanding how variadic functions work
Let’s create our own variadic function. We will write a simple program to find whether an integer exists in an input list of integers.
1package main
2
3import (
4 "fmt"
5)
6
7func find(num int, nums ...int) {
8 fmt.Printf("type of nums is %T\n", nums)
9 found := false
10 for i, v := range nums {
11 if v == num {
12 fmt.Println(num, "found at index", i, "in", nums)
13 found = true
14 }
15 }
16 if !found {
17 fmt.Println(num, "not found in ", nums)
18 }
19 fmt.Printf("\n")
20}
21func main() {
22 find(89, 89, 90, 95)
23 find(45, 56, 67, 45, 90, 109)
24 find(78, 38, 56, 98)
25 find(87)
26}
In the above program, func find(num int, nums ...int)
in line no.7, accepts variable number of arguments for the nums
parameter. Inside the function find, the type of nums
is []int
i.e, an integer slice.
The way variadic functions work is by converting the variable number of arguments to a slice of the type of the variadic parameter. For instance, in line no. 22 of the program above, the variable number of arguments to the find
function are 89, 90, 95. The find function expects a variadic int
argument. Hence these three arguments will be converted by the compiler to a slice of type int []int{89, 90, 95}
and then it will be passed to the find
function.
In line no. 10, the for
loop ranges over the nums
slice and prints the position of num
if it is present in the slice. If not, it prints that the number is not found.
The above program outputs,
type of nums is []int
89 found at index 0 in [89 90 95]
type of nums is []int
45 found at index 2 in [56 67 45 90 109]
type of nums is []int
78 not found in [38 56 98]
type of nums is []int
87 not found in []
In line no. 25 of the above program, the find function call has only one argument. We have not passed any argument to the variadic nums ...int
parameter. As discussed earlier, this is perfectly legal and in this case, nums
will be a nil
slice with length and capacity 0.
Slice arguments vs Variadic arguments
We should definitely have a question lingering in your mind now. In the above section, we learned that the variadic arguments to a function are in fact converted a slice. Then why do we even need variadic functions when we can achieve the same functionality using slices? I have rewritten the program above using slices below.
1package main
2
3import (
4 "fmt"
5)
6
7func find(num int, nums []int) {
8 fmt.Printf("type of nums is %T\n", nums)
9 found := false
10 for i, v := range nums {
11 if v == num {
12 fmt.Println(num, "found at index", i, "in", nums)
13 found = true
14 }
15 }
16 if !found {
17 fmt.Println(num, "not found in ", nums)
18 }
19 fmt.Printf("\n")
20}
21func main() {
22 find(89, []int{89, 90, 95})
23 find(45, []int{56, 67, 45, 90, 109})
24 find(78, []int{38, 56, 98})
25 find(87, []int{})
26}
The following of the advantages of using variadic arguments instead of slices.
- There is no need to create a slice during each function call. If you look at the program above, we have created new slices during each function call in line nos. 22, 23, 24 and 25. This additional slice creation can be avoided when using variadic functions
- In line no.25 of the program above, we are creating an empty slice just to satisfy the signature of the
find
function. This is totally not needed in the case of variadic functions. This line can just befind(87)
when variadic function is used. - I personally feel that the program with variadic functions is more readable than the once with slices :)
Append is a variadic function
Have you ever wondered how the append function in the standard library used to append values to a slice accepts any number of arguments. It’s because it’s a variadic function.
func append(slice []Type, elems ...Type) []Type
The above is the definition of append
function. In this definition elems
is a variadic parameter. Hence append can accept a variable number of arguments.
Passing a slice to a variadic function
Let’s pass a slice to a variadic function and find out what happens from the below example.
1package main
2
3import (
4 "fmt"
5)
6
7func find(num int, nums ...int) {
8 fmt.Printf("type of nums is %T\n", nums)
9 found := false
10 for i, v := range nums {
11 if v == num {
12 fmt.Println(num, "found at index", i, "in", nums)
13 found = true
14 }
15 }
16 if !found {
17 fmt.Println(num, "not found in ", nums)
18 }
19 fmt.Printf("\n")
20}
21func main() {
22 nums := []int{89, 90, 95}
23 find(89, nums)
24}
In line no. 23, we are passing a slice to a function that expects a variable number of arguments.
This will not work. The above program will fail with compilation error ./prog.go:23:10: cannot use nums (type []int) as type int in argument to find
Why does this don’t work? Well, it’s pretty straight forward. The signature of the find
function is provided below,
1func find(num int, nums ...int)
According to the definition of a variadic function, nums ...int
means that it will accept a variable number of arguments of type int
.
In line no. 23 of the program above, nums
which is []int slice is passed to the find
function which is expecting a variadic int
argument. As we already discussed, these variadic arguments will be converted to a slice of type int
since find
expects variadic int arguments. In this case, nums
is already a []int
slice and the compiler tries to create a new []int
i.e the compiler tries to do
find(89, []int{nums})
which will fail since nums
is a []int
and not a int
.
So is there a way to pass a slice to a variadic function? The answer is yes.
There is a syntactic sugar which can be used to pass a slice to a variadic function. You have to suffix the slice with ellipsis ...
If that is done, the slice is directly passed to the function without a new slice being created.
In the above program if you replace find(89, nums)
in line no. 23 with find(89, nums...)
, the program will compile and print the following output.
type of nums is []int
89 found at index 0 in [89 90 95]
Here is the full program for your reference.
1package main
2
3import (
4 "fmt"
5)
6
7func find(num int, nums ...int) {
8 fmt.Printf("type of nums is %T\n", nums)
9 found := false
10 for i, v := range nums {
11 if v == num {
12 fmt.Println(num, "found at index", i, "in", nums)
13 found = true
14 }
15 }
16 if !found {
17 fmt.Println(num, "not found in ", nums)
18 }
19 fmt.Printf("\n")
20}
21func main() {
22 nums := []int{89, 90, 95}
23 find(89, nums...)
24}
Gotcha
Just be sure you know what you are doing when you are modifying a slice inside a variadic function.
Let’s look at a simple example.
1package main
2
3import (
4 "fmt"
5)
6
7func change(s ...string) {
8 s[0] = "Go"
9}
10
11func main() {
12 welcome := []string{"hello", "world"}
13 change(welcome...)
14 fmt.Println(welcome)
15}
What do you think will be the output of the above program? If you think it will be [Go world]
Congrats! you have understood variadic functions and slices. If you got it wrong, no big deal, let me explain how we get this output.
In line no. 13 of the program above, we are using the syntactic sugar ...
and passing the slice as a variadic argument to the change
function.
As we have already discussed, if ...
is used, the welcome
slice itself will be passed as an argument without a new slice being created. Hence welcome
will be passed to the change
function as argument.
Inside the change function, the first element of the slice is changed to Go
. Hence this program outputs
[Go world]
Here is one more program to understand variadic functions.
1package main
2
3import (
4 "fmt"
5)
6
7func change(s ...string) {
8 s[0] = "Go"
9 s = append(s, "playground")
10 fmt.Println(s)
11}
12
13func main() {
14 welcome := []string{"hello", "world"}
15 change(welcome...)
16 fmt.Println(welcome)
17}
I would leave it as an exercise for you to figure out how the above program works :).
That’s it for variadic function. Thanks for reading. Please leave your valuable feedback and comments. Have a good day.
Please consider sharing this tutorial on twitter or LinkedIn.
Next tutorial - Maps