Variadic functions in Golang

In many cases, multiple parameters are needed to be passed in a function. Sometimes we know how many and other times we don’t. Golang variadic function applies to the second case where we don’t know how many arguments could be passed when calling that function.

What are variadic functions in Golang?

Variadic functions are just like other functions. The only difference is its parameter defined in a way that it allows for an unknown number of arguments to be passed when calling the function.

Declaration of a variadic function

Declaring a variadic function is really simple. We use the pack operator to pass an arbitrary number of arguments in that function. In the underlying structure, a slice is passed down in that function. Here is shown how we declare the function that way.

func funcName(param ...paramType) returnType {}

Built-in variadic functions

There are several variadic functions we use all the time. All of them are built-in. Here is the variadic function fmt.Printf() in action.

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("Hello world, the number is: %d, the float is: %f", 42, 161.9999)      // Hello world, the number is: 42, the float is: 161.999900
}

The function above is a variadic function whose first argument is a string and the second argument is a variadic argument that means it takes an arbitrary number of the argument.

When to use a variadic function?

Anytime a function is not bound to a specific number of arguments that function can simply become a variadic function. The best case for variadic function when we are unsure of how many arguments will be passed.

Passing parameters into a variadic function

Variadic functions as explained can take any number of arguments and even any type of argument. Below is an example illustrating the property of a variadic function. Interfaces can help when providing multi-type slice as an argument.

package main

import (
	"fmt"
)

func variadicFunc(arg ...int) {
	for i := range arg {
		fmt.Println(arg[i])
	}
}

// pass a slice of interface{}
func printAll(arg ...interface{}) {
	fmt.Println(arg...)  // forward it here
}

func main() {
	variadicFunc(1, 2, 3)    // 3 arguments
	
	// output:
	// 1
	// 2
	// 3
	
	variadicFunc(3, 4, 5, 6, 7, 8)  // 6 arguments
	
	// output: 
	// 3
	// 4
	// 5
	// 6
	// 7
	// 8

        // pass all types of arguments using interface
        printAll(1, "Jane", 3.4, 4.7890, 'a')     // prints 1 Jane 3.4 4.789 97
}

Pack and unpack operator

There are two operators that can create confusion while using variadic arguments. The pack and unpack operator. The pack operator is used when defining variadic functions since the arguments are packed into a slice when passed in that function. The unpack operator unpacks the content of the slice. Here is an example of showing how both operators work.

package main

import (
	"fmt"
)

func v1(names ...string) {        // pack operator used before type in argument
	fmt.Println(names)
}

func main() {

	var names []string = []string{"Albert", "Issac"}
	
	v1("John", "Jane", "Dexter", "Bruce")     // [John Jane Dexter Bruce]
	
	// Here is unpack operator in action
	v1(names...)                              // [Albert Issac]
}

Get passed arguments in Variadic Function

Accessing the parameters of a variadic function is really simple. We can access them in an indexed way as shown below.

package main

import (
	"fmt"
)

func f(arg ...int) {

	// print first argument
	fmt.Println(arg[0]) 
	
	// print second argument
	fmt.Println(arg[1])
}

func main() {
	f(1, 2, 4)     // prints 1 2
}

The mutability of a passed parameter

When a variadic function takes argument they take those and add to a slice. And a slice is essentially a reference to an array in Go. So, the passed param if modified in any way mutates the original data.

package main

import (
	"fmt"
)

func f(arg ...int) {
	// print contents of the arg slice
	fmt.Println(arg)
	
	// modify data
	arg[0] = 0
}

func main() {
	var s []int = []int{1, 2, 3}

	f(s...)     
	
	// output:
	// [1 2 3]
	// [0 2 3]
	
	fmt.Println(s)
	
	// output:
	// [0 2 3]        // observe that the slice has been modified
}

Adding non-variadic params

Non-variadic params can be passed with a variadic param. First, pass all non-variadic ones then variadic one as shown here.

package main

import (
	"fmt"
)

func doWork(a int, b string, num ...int) {
	fmt.Println(num)
}

func main() {
	doWork(1, "Jane", 3, 4)    // prints [3 4]
}

You can see that variadic functions are extremely useful and can be used in many different ways.