7 minutes read

You already know some things about functions in Golang. Every function has a name, a list of parameters, a return type, and a function body. It's possible to divide functions based on the return statement: a standard function (with return) and a procedure (just actions, without return).

What you maybe didn't know is that Golang has a third type of functions. What if you learned that you can create a function with an indefinite number of arguments?

Welcome to a new topic about functions. You'll get acquainted with a special type of function called variadic. You will learn how to create and use it, and where you have already met them.

Variadic functions

Variadic functions are a type of function that can take an indefinite number of arguments of the same type as declared in the function's parameter list.

There are many operations where variadic functions are commonly used, such as arithmetic operations, string concatenation, logical operations, and even input/output operations. Below are a few examples of regular functions:

Sum(1, 2) // 3
Concat("Hello ", "World!") // "Hello World!"

The Sum() function calculates the total sum of numbers, and the Concat() function concatenates strings. Nothing special. Just standard functions with two parameters. Let's imagine a signature of the Sum() function:

func Sum(a int, b int) int {}

What if you need to add a third value? Do you have to create a new function? Or use a code similar to this: Sum(Sum(1, 2), 3)? Of course not. The Sum() and Concat() functions can be variadic. With variadic functions, you can pass a lot more arguments. For example:

Sum(1, 2, 3, 4, 5) // 15
Concat("Hello ", "World! ", "I'm alive!") // "Hello World! I'm alive!"

Functions are still the same, but you can use an unlimited count of arguments! The signature of such functions looks like this:

func Sum(nums ...int) int {}
func Concat(strings ...string) string {}

Here is the first rule of using a variadic function: its arguments must have the same type. You can declare a variadic function by preceding the type of the final parameter with an ellipsis .... This syntax allows us to call the function with zero to multiple values of the specified type.

Variadic functions in the fmt package

You have already seen variadic functions when using the fmt package like fmt.Scan() or fmt.Println():

package main

import "fmt"

func main() {
    var number int
    var line string

    fmt.Scan(&number, &line) // Input: 42 Galaxy

    fmt.Println("The number is", number) // The number is 42
    fmt.Printf("The line is %s\n", line) // The line is Galaxy
}

You've read above that variadic functions could take a varying number of arguments of the same type. However, you might be wondering how the fmt.Scan() and fmt.Println() functions can take arguments of both int and string types?

To explain this in more detail, let's take a look at the documentation for both functions:

func Println(a ...any) (n int, err error) {}
func Scan(a ...any) (n int, err error) {}

Take notice that any is a special type in Go, for the cases when any data type is suitable, or when the process of the function doesn't depend on the specific type of variable.

As you can see, both Println() and Scan() functions' parameter lists have the (a ...any) parameter declaration. This means they can take a varying number of arguments of any type.

Now let's take a closer look at the fmt.Printf() function:

func Printf(format string, a ...any) (n int, err error) {}

In the function parameter list of fmt.Printf() the first parameter is the format string, and the last parameter is the variadic parameter a ...any. Here is the second main rule of using variadic functions: you must place the variadic parameter with ellipsis ... at the end of the parameter list.

Variadic functions in the builtin package

Another example of a variadic function included in Go's standard library is the append() function from the builtin package:

func append(slice []Type, elems ...Type) []Type {}

The append() function takes a slice and adds a variadic number elems of the same data type to the end of the slice.

...

func main() {
    numbers := []int{1, 2, 3} // [1 2 3] 
    numbers = append(numbers, 4, 5, 6) // [1 2 3 4 5 6]
}

Take notice that apart from append(), there are other variadic functions in the builtin package, such as print() and println().

Creating a variadic function

Let's return to the first section and make the functions work. All the functions have the same signature: they get a list of values and process the same type of actions (addition or concatenation).

For now, you still don't know how to process the argument provided by the ellipsis. Here is the last main fact about variadic functions: the ellipsis argument is just a slice. Pretty simple! To process the slice, you can use a loop:

...

func main() {
    fmt.Println(Sum(1, 2, 3)) // 6
    fmt.Println(Concat("Hello ", "World! ", "I'm alive!")) // Hello World! I'm alive!
}

func Sum(nums ...int) int {
    var sum int = 0
    for _, n := range nums {
        sum += n
    }

    return sum
}

func Concat(strings ...string) string {
    var result string = ""
    for _, s := range strings {
        result += s
    }

    return result
}

Finally, let's create a variadic function with two arguments. Let the first parameter be the divisible, and other parameters (they will be variadic) be dividers. The function will return true if it is possible to divide the divisible by all dividers without remainder, or in other words, if all of the dividers are divisors. Otherwise, it will return false. Below is the code for this function:

...

func main() {
    fmt.Println(divisorsCheck(12, 2, 3, 4, 6)) // true
    fmt.Println(divisorsCheck(12, 1, 2, 3, 5)) // false
}

func divisorsCheck(divisible int, divisors ...int) bool {
    for _, d := range divisors {
        if divisible%d != 0 {
            return false
        }
    }

    return true
}

The first call of the divisorsCheck() function returns true, because all numbers (2,3,4,62, 3, 4, 6) are divisors of 1212. The second call of the function returns false, because you can't divide 1212 by 55 without a remainder.

Conclusion

Sometimes you need to use an indefinite count of variables. If variadic functions didn't exist in Golang, it would be possible in two ways: using slices, or writing functions for each possible case. The first way is still suitable, but let's be honest, it's not pretty. Variadic functions increase the readability and accessibility of the code, that's why they are a better solution.

Here are the main rules to using variadic functions:

  • a function is variadic when the type of its final parameter is preceded by an ellipsis, e.g. ...int;

  • you should place a variadic parameter at the end of the parameter list;

  • variadic arguments must have the same type;

  • the ellipsis parameter is a slice.

28 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo