Variadic functions in Go

A variadic function accepts an infinite number of arguments and all these arguments are stored in a parameter of the slice type.

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 both pack and unpack 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 ... like slice... 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 to getMultiples function as second argument. Obviously, compiler will complain cannot use s (type []int) as type int in argument to getMultiples because slice is type of []int and getMultiples expects parameter(s) of int.

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.

#golang #functions