Go Functions — func Multiple Returns Named Returns and Variadic Parameters
In this tutorial, you will learn about Go Functions. We cover key concepts, practical examples, and best practices to help you master this topic.
Go functions defined with func support multiple return values for error handling, named returns for self-documenting code, variadic parameters with ... syntax, and first-class function values for callbacks.
What You'll Learn
- Defining functions with parameters and returns
- Multiple return values and named returns
- Variadic functions with ...
- First-class functions and closures
Why It Matters
Functions are the building blocks of Go programs. Docker uses functions for each CLI command, Kubernetes controllers are built from function chains. Doda Browser uses functions for HTTP handlers and middleware. Understanding Go's function patterns is essential.
flowchart LR
A["Functions"] --> B["Declaration"]
B --> C["Returns"]
C --> D["Multiple Returns"]
D --> E["Variadic"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Basic Function Declaration
package main
import "fmt"
func greet(name string) string {
return "Hello, " + name + "!"
}
func main() {
msg := greet("Alice")
fmt.Println(msg)
}
Multiple Return Values
Go functions can return multiple values, commonly used for returning results with errors:
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
func main() {
result, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Result:", result)
}
Ignoring Return Values
result, _ := divide(10, 2) // Ignore error (not recommended)
Named Return Values
Named returns document the meaning of each return value:
func rectangle(width, height float64) (area float64, perimeter float64) {
area = width * height
perimeter = 2 * (width + height)
return // Naked return — returns area and perimeter
}
func main() {
a, p := rectangle(5, 3)
fmt.Println("Area:", a)
fmt.Println("Perimeter:", p)
}
Named returns are initialized to their zero values and a bare return returns them.
Variadic Functions
Functions that accept a variable number of arguments:
func sum(numbers ...int) int {
total := 0
for _, n := range numbers {
total += n
}
return total
}
func main() {
fmt.Println(sum(1, 2)) // 3
fmt.Println(sum(1, 2, 3, 4, 5)) // 15
// Slice to variadic
nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 60
}
Function Values
Functions are first-class in Go — they can be assigned to variables, passed as arguments, and returned:
func main() {
// Assign function to variable
double := func(x int) int {
return x * 2
}
fmt.Println(double(5)) // 10
// Pass function as argument
numbers := []int{1, 2, 3, 4, 5}
transformed := mapValues(numbers, double)
fmt.Println(transformed) // [2 4 6 8 10]
}
func mapValues(values []int, f func(int) int) []int {
result := make([]int, len(values))
for i, v := range values {
result[i] = f(v)
}
return result
}
Closures
Functions that capture their surrounding scope:
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
c1 := counter()
c2 := counter()
fmt.Println(c1()) // 1
fmt.Println(c1()) // 2
fmt.Println(c2()) // 1 (independent counter)
}
Anonymous Functions
Functions without a name, often used for goroutines or short callbacks:
func main() {
// Inline anonymous function
result := func(a, b int) int {
return a + b
}(3, 4)
fmt.Println(result) // 7
// Goroutine with anonymous function
go func() {
fmt.Println("Running in goroutine")
}()
}
Deferred Function Calls
Defer schedules execution until the surrounding function returns:
func processFile(name string) error {
f, err := os.Open(name)
if err != nil {
return err
}
defer f.Close() // Runs when processFile returns
// Process file...
return nil
}
Deferred functions are executed in LIFO order and can access named return values:
func countLines(path string) (lines int, err error) {
f, err := os.Open(path)
if err != nil {
return 0, err
}
defer f.Close()
defer func() {
fmt.Printf("File %s has %d lines\n", path, lines)
}()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines++
}
return lines, scanner.Err()
}
Common Mistakes
1. Unused Parameters
// Won't compile
func greet(name string, unused int) string {
return "Hello, " + name
}
// Use _ for unused parameters
func greet(name string, _ int) string {
return "Hello, " + name
}
2. Shadowing Variables with :=
func example() error {
x, err := doSomething()
if err != nil {
return err
}
// Forgot to use x...
x, err = doSomethingElse() // := would create new shadow variables
return err
}
3. Naked Returns with Complex Logic
Naked returns (return without values) are only clear for short functions. In long functions, explicit returns improve readability.
4. Not Checking Returned Errors
result, _ := doSomething() // Error ignored!
5. Confusing Parameter Types with Return Types
// Return type comes after parameters
func add(a, b int) int { ... }
Practice Questions
1. How do you return multiple values from a Go function?
List multiple types in the return signature: func divide(a, b float64) (float64, error). Callers destructure with result, err := divide(1, 2).
2. What are named return values?
Named return values are declared in the function signature with names. They are initialized to zero values and can be returned with a bare return statement.
3. How do variadic functions work?
Variadic functions accept any number of trailing arguments using ...: func sum(nums ...int) int. The arguments become a slice inside the function.
4. What is a closure?
A closure is a function value that references variables from outside its body. The function captures the variables and can access them even when the enclosing function has returned.
Challenge: Write a function that takes a slice of integers and a predicate function, returning two slices: those matching and those not matching.
Solution
package main
import "fmt"
func partition(nums []int, pred func(int) bool) ([]int, []int) {
var match, rest []int
for _, n := range nums {
if pred(n) {
match = append(match, n)
} else {
rest = append(rest, n)
}
}
return match, rest
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6}
evens, odds := partition(nums, func(n int) bool {
return n%2 == 0
})
fmt.Println("Evens:", evens)
fmt.Println("Odds:", odds)
}
FAQ
{{< faq question="Can I have default parameter values in Go?" >}} No. Go doesn't support default parameter values. Use variadic options or helper constructor functions instead. {{< /faq >}}
{{< faq question="What's the difference between parameters and arguments?" >}} Parameters are the variables in the function definition. Arguments are the values passed when calling the function. {{< /faq >}}
{{< faq question="Can I overload functions in Go?" >}} No. Go doesn't support function overloading. Each function must have a unique name within a package. {{< /faq >}}
{{< faq question="How do I define a function inside another function?" >} Use an anonymous function assigned to a variable or defined inline. Nested functions must be anonymous — you can't declare a named function inside another. {{< /faq >}}
{{< faq question="What is init function?" >}
init() is a special function that runs automatically before main(). It's used for initialization and doesn't take parameters or return values. You can have multiple init functions in a package.
{{< /faq >}}
Try It Yourself
package main
import (
"fmt"
"strings"
)
func transform(words []string, fn func(string) string) []string {
result := make([]string, len(words))
for i, w := range words {
result[i] = fn(w)
}
return result
}
func main() {
words := []string{"hello", "world", "go", "programming"}
upper := transform(words, strings.ToUpper)
fmt.Println("Upper:", upper)
prefix := transform(words, func(s string) string {
return "!!" + s + "!!"
})
fmt.Println("Prefixed:", prefix)
// Counter closure
counter := func() func() int {
count := 0
return func() int {
count++
return count
}
}()
fmt.Println("Count:", counter())
fmt.Println("Count:", counter())
fmt.Println("Count:", counter())
}
Expected output:
Upper: [HELLO WORLD GO PROGRAMMING]
Prefixed: [!!hello!! !!world!! !!go!! !!programming!!]
Count: 1
Count: 2
Count: 3
What's Next
Now that you understand functions, learn about arrays and slices for working with collections.
| Topic | Description | Link |
|---|---|---|
| Go Arrays & Slices | Fixed arrays, slice, append, make | {{< ref "07-arrays-slices" >}} |
| Go Maps & Structs | map, struct, tags, zero values | {{< ref "08-maps-structs" >}} |
| Rust Functions | Compare Rust functions | Rust |