Session 2 · Functions, closures and defer#
The big idea#
A function packages a few lines of code under a name so you can use it again. Go functions have two habits that surprise people coming from other languages: they can return more than one value, and they can defer a clean-up step so it runs no matter how the function ends.
Everyday example: a vending machine. You press a button (the call), it hands back your snack and your change (two return values). Whatever happens inside, the machine always locks its door again at the end (a deferred clean-up).
In plain words
func name(inputs) outputs { ... }. Return several things at once, usually “the answer, and
whether it worked”. Use defer right after you open something, so you cannot forget to close it.
Reach for it when:
a function can fail and the caller must know (return a value and an
errororok)you want to run clean-up on every exit path
you need a small piece of state hidden inside a function (a closure)
Functions#
funcs.go: parameters, multiple results, named results and variadic arguments.
package main
import "fmt"
func add(a, b int) int { return a + b }
// two results: a value and whether it worked
func divide(a, b float64) (float64, bool) {
if b == 0 {
return 0, false
}
return a / b, true
}
// named results
func minMax(nums []int) (min, max int) {
min, max = nums[0], nums[0]
for _, n := range nums {
if n < min {
min = n
}
if n > max {
max = n
}
}
return
}
// variadic: any number of ints
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
func main() {
fmt.Println(add(2, 3))
q, ok := divide(10, 4)
fmt.Println(q, ok)
_, ok = divide(1, 0)
fmt.Println(ok)
lo, hi := minMax([]int{4, 9, 1, 7})
fmt.Println(lo, hi)
fmt.Println(sum(), sum(1, 2, 3))
xs := []int{10, 20}
fmt.Println(sum(xs...))
}
5
2.5 true
false
1 9
0 6
30
Same-typed parameters can share a type:
a, b int.(float64, bool)returns two values. The caller takes both, or ignores one with_.Named results (
min, max int) are variables you can fill in; a barereturnsends them back. Use them for short functions where the names document the result.nums ...intaccepts any number of ints and arrives as a slice. Pass an existing slice withxs....
Closures and defer#
closures_defer.go: a function that remembers, and clean-up that runs at the end.
package main
import "fmt"
func counter() func() int {
n := 0
return func() int {
n++
return n
}
}
func work() {
defer fmt.Println("cleanup 1")
defer fmt.Println("cleanup 2")
fmt.Println("working...")
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
other := counter() // its own private n
fmt.Println(other())
work()
for i := 1; i <= 3; i++ {
defer fmt.Println("deferred", i)
}
fmt.Println("end of main")
}
1 2 3
1
working...
cleanup 2
cleanup 1
end of main
deferred 3
deferred 2
deferred 1
A closure is a function that captures variables from where it was written. Each call to
counter()makes a freshnthat only the returned function can see.deferwaits until the surrounding function returns. Several defers run last in, first out, like a stack of plates.Deferred call arguments are worked out immediately:
defer fmt.Println("deferred", i)saves the currentiright now.
Note
Functions are values in Go. You can store one in a variable, pass it to another function and return
it, which is exactly how counter works.
Common mistakes#
Ignoring the second result.
result, _ := divide(1, 0)throws away the “did it work” signal.deferinside a long loop. Deferred calls only run when the function ends, so a loop that opens 10,000 files withdefer f.Close()keeps them all open. Close inside a helper function.Overusing named results. In a long function a bare
returnhides what is being returned.Mixing up
...on both sides.nums ...intdeclares;xs...unpacks a slice at the call.
Hands-on exercises#
Exercise 1 · Stats. Write average(nums ...float64) (float64, bool) that returns false when
called with no numbers.
Solution
package main
import "fmt"
func average(nums ...float64) (float64, bool) {
if len(nums) == 0 {
return 0, false
}
total := 0.0
for _, n := range nums {
total += n
}
return total / float64(len(nums)), true
}
func main() {
fmt.Println(average(2, 4, 9))
fmt.Println(average())
}
5 true
0 false
Exercise 2 · Adder factory. Write makeAdder(n int) func(int) int so that add5 := makeAdder(5)
gives add5(3) == 8.
Solution
package main
import "fmt"
func makeAdder(n int) func(int) int {
return func(x int) int { return x + n }
}
func main() {
add5 := makeAdder(5)
add10 := makeAdder(10)
fmt.Println(add5(3), add10(3))
}
8 13
Each adder remembers its own n.
Exercise 3 · Timer with defer. Write slowJob() that prints start, then end from a deferred
call, and in between prints working. Predict the order before running.
Solution
package main
import "fmt"
func slowJob() {
fmt.Println("start")
defer fmt.Println("end")
fmt.Println("working")
}
func main() {
slowJob()
}
start
working
end
defer registers the call when it is reached, but runs it as the function returns.