The power of defer in Go: on entry

Here is a small tip I wish I would have learned earlier when I started coding with Golang: using defer in both the start and exit of any function.

Although this is a very basic concept that can be extremely helpful when debugging code, it is difficult to find any information about it in most of the Golang tutorials you can find on the Internet.

Defer is one of the first things you learn when starting to code with Go. It is pretty straightforward: inside any function, you prefix some other function with defer to make sure this function is executed right before the parent function exits, no matter what. Even if the parent function panics, this defered function will run.

In case you are not familiarized with defer, take a look here before continuing reading this article: Go by Example: Defer.

However, there is something that you may haven’t read on any of the examples or tutorials you have found: you can also use defer to pair code at the start and the end of any function.

To use this, create a function and make it return another function itself, which will be the real deferred function. Defer this parent function and add extra parenthesis to it when deferring it, like this:

func main() {
    defer greet()() 
    fmt.Println("Some code here...")
}

func greet() func() {
    fmt.Println("Hello!")
    return func() { fmt.Println("Bye!") } // this will be deferred
}

Which outputs this:

Hello!
Some code here...
Bye!

The function returned by the deferred function will be the real deferred function. The other code inside the deferred function will be executed right at the start of the function (or wherever you place it).

Why is this awesome? Well, as you can see in the next example, you can use variables on the deferred function that can be accessed both on the first execution and the deferred execution:

func main() {
    example()
    otherExample()
}

func example(){
    defer measure("example")()
    fmt.Println("Some code here")
}

func otherExample(){
    defer measure("otherExample")()
    fmt.Println("Some other code here")
}

func measure(name string) func() {
    start := time.Now()
    fmt.Printf("Starting function %s\n", name)
    return func(){ fmt.Printf("Exiting function %s after %s\n", name, time.Since(start)) }
}

Which outputs this:

Starting example
Some code here
Exiting example after 0s
Starting otherExample
Some other code here
Exiting otherExample after 0s

As you can imagine, deferring code to use it on the entry and the exit of any function can be extremely useful, especially for debugging your code.

You can see another example here: https://github.com/adonovan/gopl.io/blob/master/ch5/trace/main.go


go

417 Words

2019-04-22 10:41 +0200