Part 12: Variadic Functions

Welcome to the part 12 of Golang tutorial series.

What is a variadic function?

A variadic function is a function that can accept variable number of arguments.

Syntax

If the last parameter of a function is denoted by ...T, then the function can accept any number of arguments of type T for the last parameter.

Please note that only the last parameter of a function is allowed to be variadic.

Examples and understanding how variadic functions work

Have you ever wondered how the append function which is used to append values to a slice accepts any number of arguments. Its because its 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.

Lets create our own variadic function. We will write a simple program to find whether a integer exists in a input list of integers.

package main

import (  
    "fmt"
)

func find(num int, nums ...int) {  
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    find(89, 89, 90, 95)
    find(45, 56, 67, 45, 90, 109)
    find(78, 38, 56, 98)
    find(87)
}

Run in playground

In the above program, func find(num int, nums ...int) accepts variable number of arguments for the nums parameter. Within the function find, the type of nums is equivalent to []int i.e, an integer slice.

The way variadic functions work is by converting the variable number of arguments passed, to a new 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. This is perfectly legal and in this case nums is a nil slice with length and capacity 0.



Passing a slice to a variadic function

Lets pass a slice to a variadic function and find out what happens from the below example.

package main

import (  
    "fmt"
)

func find(num int, nums ...int) {  
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    nums := []int{89, 90, 95}
    find(89, nums)
}

Run in playground

In line no. 23, we are passing a slice to a function which expects a variable number of arguments.

This will not work. The above program will fail with compilation error main.go:23: cannot use nums (type []int) as type int in argument to find

Why does this doesn't work? Well its pretty straight forward. The signature of the find function is provided below,

func find(num int, nums ...int)  

According to the definition of 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 is passed as a variadic argument to the find function. 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 a new []int slice is tried to be created using nums i.e the compiler tries to do

find(89, []int{nums})  

which will fail since nums is a []int and not an 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 ... 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 output

type of nums is []int  
89 found at index 0 in [89 90 95]  

Here is the full program for your reference.

package main

import (  
    "fmt"
)

func find(num int, nums ...int) {  
    fmt.Printf("type of nums is %T\n", nums)
    found := false
    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            found = true
        }
    }
    if !found {
        fmt.Println(num, "not found in ", nums)
    }
    fmt.Printf("\n")
}
func main() {  
    nums := []int{89, 90, 95}
    find(89, nums...)
}

Run in playground

Gotcha

Just be sure you know what you are doing when you are modifying a slice inside a variadic function.

Lets look at a simple example.

package main

import (  
    "fmt"
)

func change(s ...string) {  
    s[0] = "Go"
}

func main() {  
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}

Run in playground

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.

package main

import (  
    "fmt"
)

func change(s ...string) {  
    s[0] = "Go"
    s = append(s, "playground")
    fmt.Println(s)
}

func main() {  
    welcome := []string{"hello", "world"}
    change(welcome...)
    fmt.Println(welcome)
}

Run in playground

I would leave it as an exercise for you to figure out how the above program works :).

Thats it for variadic function. Thanks for reading. Please leave your valuable feedback and comments. Have a good day.

Next tutorial - Maps