The defer
keyword in Go is used to…literally…defer something to the end of a function block.
It’s saying “do stuff, then do this at the very end right before this function is finished”.
Example:
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
counts := make(map[string]int) // map to put in counts
files := os.Args[1:] // filenames
for _, arg := range files {
f, err := os.Open(arg)
defer closeFile(f) // this happens later
if err != nil {
fmt.Fprintf(os.Stderr, "err: %v\n", err)
continue
}
input := bufio.NewScanner(f)
for input.Scan() {
counts[input.Text()]++
}
}
for line, n := range counts {
fmt.Printf("%d\t%s\n", n, line)
}
}
func closeFile(f *os.File) {
fmt.Println("closing")
err := f.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
}
// go run main.go input.txt input.txt
// 3 sup bro
// 3 bitch
// 4 yoyo
// 8 hey
// 32 yo
// 1 heyeyeye
// closing
// closing
The line defer closeFile(f)
here is saying “after all this following stuff is done, close the file”. It’s important to close the file after reading it, so we can see that defer
keyword helps us follow good practices for cleanup stuff.
Note: This happens at the end of the enclosing function, not the enclosing block. So in the above example, we see “closing” run twice at the end before the loop exits, not the end of the loop iteration.
Other things to note here: There is no while
keyword in go, see the line for input.Scan()
— this functions as a while loop.
defer
as a hook
The term “hook” here is my invention. However, defer
can be used as a hook for entry/exit of a function. It can be used for cleanup, or for catching errors. The pattern here for doing this is often using:
- named parameters (
error
, etc.) - function literals, like an iife in JS
func handleWork() result string, err error {
defer func() {
if p := recover(); p != nil {
fmt.Errorf("work panic: %s", err)
} else {
fmt.Printf("work was successful: %s", result)
}
}()
/*
do some heavy work
*/
return result, err
}