Welcome to part 20 of the Go programming tutorial, where we will be introducing the defer
statement and its use in Go.
The idea of the defer
statement is to put off (defer) the execution of something until the surrounding function is done. The defer statement will be evalutated immediately, but wont run until the end, so you can, and usually will, put the defer
statement early in the function. The main idea here is to do this to ensure things like files are closed and connections are terminated. You can write your code to do these things at the end of a function, but, consider what can happen if you hit an error, or something goes wrong. The file might not actually ever be closed or the connection terminated. In our case, we wanted to talk about defer
because we have a "wait group" for our goroutines, and we wanted to be certain we tell this group a goroutine is "done," even if something goes wrong during that goroutine. Let's see a basic example:
package main import "fmt" func foo() { defer fmt.Println("Done!") fmt.Println("Doing some stuff, who knows what?") } func main() { foo() }
If we run this:
Doing some stuff, who knows what? Done!
As you can see, despite being written out first, the deferred
function call happens last. What if we had two defers? How might that work?
package main import "fmt" func foo() { defer fmt.Println("Done!") defer fmt.Println("Are we done yet?") fmt.Println("Doing some stuff, who knows what?") } func main() { foo() }
Output here would is:
Doing some stuff, who knows what? Are we done yet? Done!
How come? The defer
statement works on a "first in last out" structure, so the last defer will also be the first one that runs. To further drive this point home, we could change the foo
function to instead be:
func foo() { for i := 0; i < 5; i++ { defer fmt.Println(i) } }
Then our output is:
4 3 2 1 0
Alright, getting back to our goroutine and wait group, all we need to do is change:
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func say(s string) { for i:=0; i < 3; i++ { time.Sleep(100*time.Millisecond) fmt.Println(s) } wg.Done() } func main() { wg.Add(1) go say("Hey") wg.Add(1) go say("there") wg.Wait() }
To now, rather than doing wg.Done()
at the end of the say
function, we do a defer
at the top:
func say(s string) { defer wg.Done() for i:=0; i < 3; i++ { time.Sleep(100*time.Millisecond) fmt.Println(s) } }
Now the full code:
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func say(s string) { defer wg.Done() for i:=0; i < 3; i++ { time.Sleep(100*time.Millisecond) fmt.Println(s) } } func main() { wg.Add(1) go say("Hey") wg.Add(1) go say("there") wg.Wait() }
Now we're all set! Now, if we're going to talk about defer
, we might as well also talk about panic
and recover
, which is what we'll be talking about next!