- Write functions that return several and named results
- Use variadic functions and pass functions as values
- Explain how a closure captures an outside variable
- Know the order in which
deferruns and when its arguments are evaluated
Every time you press “+” on a calculator, the same addition rule runs. In programming, a reusable piece of code like this is called a function. Go functions can return several results, take other functions as arguments and even “remember” their own state. In this lesson you will also meet defer, the keyword that postpones work until a function finishes.
Declaring functions and results
A function is declared with the func keyword: first the name, then the parameters in parentheses, and finally the result type. Parameters of the same type can be shortened: a, b int instead of a int, b int. What makes Go different is that a function can return several values: (int, int). A result you don't need can be thrown away with the blank identifier _.
package main
import "fmt"
func add(a, b int) int {
return a + b
}
func divmod(a, b int) (int, int) {
return a / b, a % b
}
func rectangle(w, h float64) (area, perimeter float64) {
area = w * h
perimeter = 2 * (w + h)
return
}
func main() {
fmt.Println(add(2, 3))
q, r := divmod(17, 5)
fmt.Println(q, r)
_, rest := divmod(10, 4)
fmt.Println(rest)
fmt.Println(rectangle(3, 2.5))
}5 3 2 2 7.5 11
In rectangle the results have names: (area, perimeter float64). Such variables are created with their zero values when the function starts, and a bare return returns their current values. As the last line shows, you can even pass a call to a multi-result function straight into Println.
Variadic functions and functions as values
If you put ... before the type of the last parameter, the function accepts any number of arguments — such a function is called variadic. fmt.Println is one of them. Inside the function this parameter is an ordinary slice. To pass an existing slice, write ... after its name: sum(scores...). In Go functions are also ordinary values: you can store them in variables, pass them to other functions and return them.
package main
import "fmt"
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func apply(n int, f func(int) int) int {
return f(n)
}
func main() {
fmt.Println(sum())
fmt.Println(sum(1, 2, 3))
scores := []int{90, 85, 70}
fmt.Println(sum(scores...))
square := func(x int) int { return x * x }
fmt.Println(apply(7, square))
}0 6 245 49
Closures
An anonymous function is created right inside the code: func(x int) int { ... }. If it uses a variable from outside, it “captures” that variable and keeps it alive — such a function is called a closure. The counter below creates a new counter on every call: each closure keeps its own count variable.
package main
import "fmt"
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
other := counter()
fmt.Println(other(), next())
}1 2 3 1 4
next and other are two independent counters: other() starts again from 1, while next() continues its own count and returns 4. The count variable lives on after counter returns, because the closure holds on to it.
var funcs []func()
for i := 0; i < 3; i++ {
i := i // needed before Go 1.22
funcs = append(funcs, func() { fmt.Print(i, " ") })
}var funcs []func()
for i := 0; i < 3; i++ {
funcs = append(funcs, func() { fmt.Print(i, " ") })
}defer: saving work for the end
The defer keyword postpones a function call until the surrounding function finishes. Whether the function ends normally or exits with an error, deferred calls always run. If there are several defers, they run in reverse order — from the last to the first (LIFO, like a stack of plates). Note: the arguments are evaluated at the moment defer is written, not later.
package main
import "fmt"
func main() {
fmt.Println("start")
for i := 1; i <= 3; i++ {
defer fmt.Println("deferred", i)
}
x := 10
defer fmt.Println("x was", x)
x = 20
fmt.Println("end, x =", x)
}start end, x = 20 x was 10 deferred 3 deferred 2 deferred 1
Look at the output: even though x later became 20, the deferred call printed 10, because the argument was evaluated on the defer line. All deferred calls ran after the end line, in reverse order.
Key points
- A function is written as
func name(params) result; parameters of the same type can be grouped:a, b int. - A Go function can return several values; an unneeded result is discarded with
_. - A
...Tparameter accepts any number of arguments; an existing slice is passed ass.... - A closure captures and keeps an outside variable; since Go 1.22 each loop iteration has its own variable.
deferpostpones a call to the end of the function, runs deferred calls in reverse order and evaluates arguments immediately.
Check yourself
10 questions. Every correct answer earns XP.
func divmod(a, b int) (int, int)?