Use of defer in Go

Go

Go Problem Overview


What is the use of defer in Go? The language documentation says it is executed when the surrounding function returns. Why not just put the code at end of given function?

Go Solutions


Solution 1 - Go

We usually use defer to close or deallocate resources.

A surrounding function executes all deferred function calls before it returns, even if it panics. If you just place a function call at the end of a surrounding function, it is skipped when panic happens.

Moreover a deferred function call can handle panic by calling the recover built-in function. This cannot be done by an ordinary function call at the end of a function.

Each deferred call is put on stack, and executed in reverse order when the surrounding function ends. The reversed order helps deallocate resources correctly.

The defer statement must be reached for a function to be called.

You can think of it as another way to implement try-catch-finally blocks.

Closing like try-finally:

func main() {
    f, err := os.Create("file")
	if err != nil {
    	panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

Closing and panic handling like try-catch-finally

func main() {
    defer func() {
        msg := recover()
        fmt.Println(msg)
    }()
    f, err := os.Create(".") // . is a current directory
	if err != nil {
    	panic("cannot create file")
    }
    defer f.Close()
    // no matter what happens here file will be closed
    // for sake of simplicity I skip checking close result
    fmt.Fprintf(f,"hello")
}

The benefit over try-catch-finally is that there is no nesting of blocks and variable scopes. This simplifies the structure of the surrounding function.

Just like finally blocks, deferred function calls can also modify the return value if they can reach the returned data.

func yes() (text string) {
    defer func() {
       text = "no"
    }()
    return "yes"
}

func main() {
    fmt.Println(yes())
}

Solution 2 - Go

There are already good answers here. I would like to mention one more use case.

func BillCustomer(c *Customer) error {
    c.mutex.Lock()
    defer c.mutex.Unlock()
    
    if err := c.Bill(); err != nil {
        return err
    }
    
    if err := c.Notify(); err != nil {
        return err
    }
    
    // ... do more stuff ...
    
    return nil
}

The defer in this example ensures that no matter how BillCustomer returns, the mutex will be unlocked immediately prior to BillCustomer returning. This is extremely useful because without defer you would have to remember to unlock the mutex in every place that the function could possibly return.

ref.

Solution 3 - Go

Well, it's not always guaranteed that your code may reach the end of the function (e.g. an error or some other condition may force you to return well ahead of the end of a function). The defer statement makes sure that whatever function is assigned to it gets executed for sure even if the function panics or the code returns well before the end of the function.

The defer statement also helps keep the code clean esp. in cases when there are multiple return statements in a function esp. when one needs to free resources before return (e.g. imagine you have an open call for accessing a resource at the beginning of the function - for which a corresponding close must be called before the function returns for avoiding a resource leak. And say your function has multiple return statements, maybe for different conditions including error checking. In such a case, without defer, you normally would call close for that resource before each return statement). The defer statement makes sure the function you pass to it is always called irrespective of where the function returns, and thus saves you from extraenous housekeeping work.

Also defer can be called multiple times in the same function. E.g.: In case you have different resources being allocated through your function which need to be eventually freed before returning, then you can call defer for each of them after allocation and these functions are executed in the reverse order of the sequence in which they were called when the function exits.

Solution 4 - Go

Key benefit of using defer - it will be called any way no matter how function will return. If an extraordinary situation would occur deferred function will be called.

So it gives nice things:

  1. Recover after panic. This allows yes realize try ... catch behavior.

  2. Not to forget clean up (close files, free memory, etc) before normal exit. You may open some resource and you have to close it before exit. But function can have several exit points - so you have to add freeing in every return point. That’s very tedious in maintenance. Or you can put only one deferred statement - and resources will be released automatically.

Solution 5 - Go

Summary:

When we do certain operations that need cleanup, we can "schedule" the cleanup operations which would be run when the function returns no matter which path that happens, including due to panic.

Detailed answer:

  1. Programming languages strive to provide constructs that facilitate simpler and less error-prone development. (E.g. why should Golang support garbage collection when we can free the memory ourselves)
  2. A function can return at multiple points. The user might overlook doing certain cleanup operations in some paths
  3. Some cleanup operations are not relevant in all return paths
  4. Also, it is better to keep the cleanup code closer to the original operation which needed the cleanup
  5. When we do certain operations that need cleanup, we can "schedule" the cleanup operations which would be run when the function returns no matter which path that happens.

Solution 6 - Go

> A defer statement defers the execution of a function until the > surrounding function returns.

This example demonstrates defer functionality:

func elapsed(what string) func() {
	start := time.Now()
	fmt.Println("start")
	return func() {
		fmt.Printf("%s took %v\n", what, time.Since(start))
	}
}

func main() {
	defer elapsed("page")()
	time.Sleep(time.Second * 3)
}

Out:

start
page took 3s

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionShubham ChaudharyView Question on Stackoverflow
Solution 1 - GoGrzegorz ŻurView Answer on Stackoverflow
Solution 2 - GoMayurView Answer on Stackoverflow
Solution 3 - GoRavi RView Answer on Stackoverflow
Solution 4 - GoEugene LisitskyView Answer on Stackoverflow
Solution 5 - Gopr-palView Answer on Stackoverflow
Solution 6 - GoBenyamin JafariView Answer on Stackoverflow