In this post, we will learn about the special defer
keyword in Go. We will take an in-depth look into deferred statements and learn their uses, as well as some common pitfalls to look out for.
If you just want to see the code, you can find the complete examples on Github
Basic Usage
The defer
keyword instructs a function to execute after the surrounding function completes.
Let’s look at a basic example:
package main
import "fmt"
func main() {
// When we add `defer` before a function, that function is executed
// after the surrounding function completes
defer fmt.Println("this is printed once the function completes")
fmt.Println("this is printed first")
fmt.Println("this is printed second")
}
In this case, the “surrounding function” is
main
Output:
// this is printed first
// this is printed second
// this is printed once the function completes
Function Scope
It is important to note that defer
is scoped to the function inside which it is declared.
Let’s look at an example where we call another function from inside main
:
package main
import "fmt"
func main() {
defer fmt.Println("this is printed once main completes")
greet()
fmt.Println("this is printed after greet is called")
}
func greet() {
// this function executes once `greet()` completes.
// It is independent of deferred functions declared elsewhere
defer fmt.Println("printed after the first greeting")
fmt.Println("first greeting")
}
This gives us the output:
// first greeting
// printed after the first greeting
// this is printed after greet is called
// this is printed once main completes
This is because the deferred function defined in greet
is run once greet
is completed, whereas the same in main
is run after main
has completed:
Execution Sequence (Call Stack)
When there are multiple deferred statements in the same function, they are stored and executed as a stack.
For example, consider a function with multiple defer
declarations:
func myFunction() {
defer fmt.Println("first")
defer fmt.Println("second")
defer fmt.Println("third")
fmt.Println("starting myFunction...")
// ...
}
When myFunction
is called, we will get this output:
starting myFunction...
third
second
first
Each time a function is deferred, it’s pushed onto a stack. Once the function execution completes, the deferred functions are popped, so that they execute in a “first-in-first-out” order:
Using Defer with Panic
In Go, a function panics if something went wrong. In this case, the function exits immediately, and doesn’t execute any statement after the panic
function call.
If any function had been deferred before panic
was called, it would be executed before the function exits:
package main
import "fmt"
func main() {
defer fmt.Println("this is printed once the function panics")
fmt.Println("this is printed before panic")
panic("something went wrong")
fmt.Println("this should not be printed")
}
This will give the following output:
// this is printed before panic
// this is printed once the function panics
// panic: something went wrong
// goroutine 1 [running]:
// main.main()
// /Users/soham/src/github.com/sohamkamani/go-defer-example/defer-with-panic.go:11 +0xe4
// exit status 2
In fact, we can even find out if panic
was called and prevent the program from exiting using the in-built recover
function.
This can only be done within deferred function calls, and provides instructions for what to do in-case panic
is called:
package main
import "fmt"
func main() {
defer func() {
// we can use the recover function inside the deferred function
// to tell if it was called after `panic` was called
if r := recover(); r != nil {
fmt.Println("recovered from panic: ", r)
}
}()
panic("something went wrong")
fmt.Println("this should not be printed")
}
Output:
// this is printed before panic
// recovered from panic: something went wrong
Argument Evaluation
Even though the deferred function is executed when the surrounding function completes, it’s arguments are evaluated immediately:
package main
import "fmt"
// i is a package level variable
var i int
// here, we increment the value of i every time this
// function is called
func count() int {
i++
return i
}
func main() {
// `count` is called here first
defer fmt.Println("deferred count:", count())
fmt.Println("current count:", count())
}
This should give you the output:
current count: 2
deferred count: 1
If you want to evaluate everything when the deferred function executes, you should ensure all functions are called within the deferred function call. Let’s see how we can do this for the above example:
package main
import "fmt"
var i int
func count() int {
i++
return i
}
func main() {
defer printCount()
fmt.Println("current count:", count())
}
// Now, count is evaluated inside `printCount`, so it will
// only be called when the deferred function executes
func printCount() {
fmt.Println("count:", count())
}
Output:
current count: 1
count: 2
func main() {
fmt.Println(zeroOrOne())
}
func zeroOrOne() (i int) {
defer func() {
i++
}()
return i
}
Deferred statements are executed before the functions value is returned.
In this case, the named return value i
is incremented once the function completes, but before the value is returned to the calling statement in the main
function.
When Should You Defer?
Defer is more like a finally
block in languages like Java or Python: something that has to be executed after attempting to run some code – whether or not it succeeds.
This is useful in cases where you have to run some clean-up operation after some code executes. For example:
- Closing a file after opening it.
- Performing clean-up actions after running a test.
Additionally, you will have to defer any function that needs to handle panics that happen within the function scope.
The main pitfalls you should take note of are argument evaluation and the order of execution for deferred functions, since these can sometimes be confusing for newcomers.
Have you used defer
in your application before? What challenges did you face? Let me know in the comments!
You can find the code for all the examples on Github