Tutorial

How To Use Variadic Functions in Go

Updated on September 12, 2019
English
How To Use Variadic Functions in Go

Introduction

A variadic function is a function that accepts zero, one, or more values as a single argument. While variadic functions are not the common case, they can be used to make your code cleaner and more readable.

Variadic functions are more common than they seem. The most common one is the Println function from the fmt package.

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

A function with a parameter that is preceded with a set of ellipses (...) is considered a variadic function. The ellipsis means that the parameter provided can be zero, one, or more values. For the fmt.Println package, it is stating that the parameter a is variadic.

Let’s create a program that uses the fmt.Println function and pass in zero, one, or more values:

print.go
package main

import "fmt"

func main() {
	fmt.Println()
	fmt.Println("one")
	fmt.Println("one", "two")
	fmt.Println("one", "two", "three")
}

The first time we call fmt.Println, we don’t pass any arguments. The second time we call fmt.Println we pass in only a single argument, with the value of one. Then we pass one and two, and finally one, two, and three.

Let’s run the program with the following command:

  1. go run print.go

We’ll see the following output:

Output
one one two one two three

The first line of the output is blank. This is because we didn’t pass any arguments the first time fmt.Println was called. The second time the value of one was printed. Then one and two, and finally one, two, and three.

Now that we have seen how to call a variadic function, let’s look at how we can define our own variadic function.

Defining a Variadic Function

We can define a variadic function by using an ellipsis (...) in front of the argument. Let’s create a program that greets people when their names are sent to the function:

hello.go
package main

import "fmt"

func main() {
	sayHello()
	sayHello("Sammy")
	sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
	for _, n := range names {
		fmt.Printf("Hello %s\n", n)
	}
}

We created a sayHello function that takes only a single parameter called names. The parameter is variadic, as we put an ellipsis (...) before the data type: ...string. This tells Go that the function can accept zero, one, or many arguments.

The sayHello function receives the names parameter as a slice. Since the data type is a string, the names parameter can be treated just like a slice of strings ([]string) inside the function body. We can create a loop with the range operator and iterate through the slice of strings.

If we run the program, we will get the following output:

Output
Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

Notice that nothing printed for the first time we called sayHello. This is because the variadic parameter was an empty slice of string. Since we are looping through the slice, there is nothing to iterate through, and fmt.Printf is never called.

Let’s modify the program to detect that no values were sent in:

hello.go
package main

import "fmt"

func main() {
	sayHello()
	sayHello("Sammy")
	sayHello("Sammy", "Jessica", "Drew", "Jamie")
}

func sayHello(names ...string) {
	if len(names) == 0 {
		fmt.Println("nobody to greet")
		return
	}
	for _, n := range names {
		fmt.Printf("Hello %s\n", n)
	}
}

Now, by using an if statement, if no values are passed, the length of names will be 0, and we will print out nobody to greet:

Output
nobody to greet Hello Sammy Hello Sammy Hello Jessica Hello Drew Hello Jamie

Using a variadic parameter can make your code more readable. Let’s create a function that joins words together with a specified delimiter. We’ll create this program without a variadic function first to show how it would read:

join.go
package main

import "fmt"

func main() {
	var line string

	line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"})
	fmt.Println(line)

	line = join(",", []string{"Sammy", "Jessica"})
	fmt.Println(line)

	line = join(",", []string{"Sammy"})
	fmt.Println(line)
}

func join(del string, values []string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}

In this program, we are passing a comma (,) as the delimiter to the join function. Then we are passing a slice of values to join. Here is the output:

Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

Because the function takes a slice of string as the values parameter, we had to wrap all of our words in a slice when we called the join function. This can make the code difficult to read.

Now, let’s write the same function, but we’ll use a variadic function:

join.go
package main

import "fmt"

func main() {
	var line string

	line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
	fmt.Println(line)

	line = join(",", "Sammy", "Jessica")
	fmt.Println(line)

	line = join(",", "Sammy")
	fmt.Println(line)
}

func join(del string, values ...string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}

If we run the program, we can see that we get the same output as the previous program:

Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

While both versions of the join function do the exact same thing programmatically, the variadic version of the function is much easier to read when it is being called.

Variadic Argument Order

You can only have one variadic parameter in a function, and it must be the last parameter defined in the function. Defining parameters in a variadic function in any order other than the last parameter will result in a compilation error:

join.go
package main

import "fmt"

func main() {
	var line string

	line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
	fmt.Println(line)

	line = join(",", "Sammy", "Jessica")
	fmt.Println(line)

	line = join(",", "Sammy")
	fmt.Println(line)
}

func join(values ...string, del string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}

This time we put the values parameter first in the join function. This will cause the following compilation error:

Output
./join_error.go:18:11: syntax error: cannot use ... with non-final parameter values

When defining any variadic function, only the last parameter can be variadic.

Exploding Arguments

So far, we have seen that we can pass zero, one, or more values to a variadic function. However, there will be occasions when we have a slice of values and we want to send them to a variadic function.

Let’s look at our join function from the last section to see what happens:

join.go
package main

import "fmt"

func main() {
	var line string

	names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

	line = join(",", names)
	fmt.Println(line)
}

func join(del string, values ...string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}

If we run this program, we will receive a compilation error:

Output
./join-error.go:10:14: cannot use names (type []string) as type string in argument to join

Even though the variadic function will convert the parameter of values ...string to a slice of strings []string, we can’t pass a slice of strings as the argument. This is because the compiler expects discrete arguments of strings.

To work around this, we can explode a slice by suffixing it with a set of ellipses (...) and turning it into discrete arguments that will be passed to a variadic function:

join.go
package main

import "fmt"

func main() {
	var line string

	names := []string{"Sammy", "Jessica", "Drew", "Jamie"}

	line = join(",", names...)
	fmt.Println(line)
}

func join(del string, values ...string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}

This time, when we called the join function, we exploded the names slice by appending ellipses (...).

This allows the program to now run as expected:

Output
Sammy,Jessica,Drew,Jamie

It’s important to note that we can still pass a zero, one, or more arguments, as well as a slice that we explode. Here is the code passing all the variations that we have seen so far:

join.go
package main

import "fmt"

func main() {
	var line string

	line = join(",", []string{"Sammy", "Jessica", "Drew", "Jamie"}...)
	fmt.Println(line)

	line = join(",", "Sammy", "Jessica", "Drew", "Jamie")
	fmt.Println(line)

	line = join(",", "Sammy", "Jessica")
	fmt.Println(line)

	line = join(",", "Sammy")
	fmt.Println(line)

}

func join(del string, values ...string) string {
	var line string
	for i, v := range values {
		line = line + v
		if i != len(values)-1 {
			line = line + del
		}
	}
	return line
}
Output
Sammy,Jessica,Drew,Jamie Sammy,Jessica,Drew,Jamie Sammy,Jessica Sammy

We now know how to pass zero, one, or many arguments, as well as a slice that we explode, to a variadic function.

Conclusion

In this tutorial, we have seen how variadic functions can make your code cleaner. While you won’t always need to use them, you may find them useful:

  • If you find that you’re creating a temporary slice just to pass to a function.
  • When the number of input params are unknown or will vary when called.
  • To make your code more readable.

To learn more about creating and calling functions, you can read How to Define and Call Functions in Go.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products


Tutorial Series: How To Code in Go

Go (or GoLang) is a modern programming language originally developed by Google that uses high-level syntax similar to scripting languages. It is popular for its minimal syntax and innovative handling of concurrency, as well as for the tools it provides for building native binaries on foreign platforms.

About the authors

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Featured on Community

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more