DEV Community

Viktoras
Viktoras

Posted on • Originally published at dizzy.zone on

Go's defer statement

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()
}
Enter fullscreen mode Exit fullscreen mode

Try it on go playground

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

Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

If you put it into a timer package, you can then use it on any function like this:

func main() {
    defer timer.New().Done()
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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))
}
Enter fullscreen mode Exit fullscreen mode

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)