Defer is the golang’s version of the more familliar finally statement of the try/catch block in languages like Java and C#. The defer allows you to perform actions when surrounding function returns or panics. Unlike finally blocks though it does not need to be placed at the bottom of the code block. You can also have multiple defer statements in a single function body. This allows for handy clean up of resources. It does have its downsides though. It does add overhead so using it everywhere might not be the best idea.
A couple of examples
My favorite use of defer statements is in coalition with sync.WaitGroup. Wait group allows you to start multiple goroutines and wait until they are finished. I use defer wg.Done() as a defer statement to let the wait group know that the routine has done its job.
func main() {
defer fmt.Println("All routines are done!")
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func(j int) {
defer wg.Done()
fmt.Println(j)
}(i)
}
wg.Wait()
}
Another use case is cleaning up resources, such as making sure to close readers. Here’s an example:
res, err := client.Do(req)
if err != nil {
// error handling goes here...
}
defer res.Body.Close()
// do something with body
Function execution timer with defer
Ocassionally I’ll want to log the execution times of different functions. This can be done through a simple timer package.
type Timer struct {
start time.Time
defaultValue string
}
func New() *Timer {
return &Timer{
start: time.Now()
}
}
func (t *Timer) End() {
fmt.Printf("Execution took %v", time.Since(t.start))
}
If you put it into a timer package, you can then use it on any function like this:
func main() {
defer timer.New().Done()
}
For extra flavor, we can improve the log message to include the caller name of the function we are profiling. All we need to do is write a function like this:
func (t *Timer) GetCallerName() (name *string) {
uintpt := make([]uintptr, 1)
n := runtime.Callers(3, uintpt)
if n == 0 {
return
}
fun := runtime.FuncForPC(uintpt[0]-1)
if fun == nil {
return
}
nameTemp := fun.Name()
name = &nameTemp
return
}
And change the End() function like so:
func (t *Timer) End() {
callerName := t.GetCallerName()
if callerName == nil {
callerName = &t.defaultValue
}
fmt.Printf("Execution of %v took %v", *callerName, time.Since(t.start))
}
Here’s a full gist of the code I ended up with.
Defer performance hit
As mentioned before, there’s a performance hit for using defer but as the benchmarks I linked to before are rather old, I made one myself. I’m not an expert on benchmarking, so please let me know if anything’s not right. Here’s the result:
Method | ns/op |
---|---|
No defer | 98986 |
With defer | 138555 |
There’s a hit, but I would not worry if you’re using defer in a reasonable manner and the performance difference of a few nanoseconds does not impact you that much. I really do prefer Go’s approach to the more standard finally block.
What about you? How do you use defer? Let me know in the comments below!
Top comments (0)