Part 12: 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

func hello(a int, b ...int) {  
}

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

hello(1, 2) //passing one argument "2" to b  
hello(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.

hello(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

func hello(b ...int, a int) {  
}

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.

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) 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.

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, []int{89, 90, 95})
    find(45, []int{56, 67, 45, 90, 109})
    find(78, []int{38, 56, 98})
    find(87, []int{})
}

Run in playground

The following of the advantages of using variadic arguments instead of slices.

  1. 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
  2. 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 be find(87) when variadic function is used.
  3. 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.

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 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,

func 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.

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.

Let's 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 :).

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

Next tutorial - Maps



Naveen Ramanathan

Naveen Ramanathan has more than 11 years of programming experience in backend and mobile app development. If you would like to hire him, please mail to naveen[at]golangbot[dot]com