74

5 Gotchas of Defer in Go (Golang) — Part I – Learn Go Programming

 6 years ago
source link: https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1-8d070894cb01?gi=a7f13e3a5978
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

#1 — Deferred nil func

If a deferred func evaluates to nil, execution panics when the surrounding func ends not when defer is called.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Example

func() {
var
run func() = nil
defer run() fmt.Println("runs")
}

Output

runs❗️ panic: runtime error: invalid memory address or nil pointer dereference

Here, the func continues until the end, after that the deferred func will run and panic because it’s nil. However, run() can be registered without a problem because it wouldn’t be called until the containing func ends.

This is a simple example but the same thing can happen in the real-world, so if you get across something like this, be suspicious about that that could have been happened because of this gotcha.

1*_rdOI_SbL9m07QBcKMqz5Q.png

1*FFqiVq-lvmbRxzT6gOCGlQ.png
1*-OQBs5b4u65NRQM8aFukAw.png

#2 — Defer inside a loop

Do not use defer in a loop unless you’re sure about what you’re doing. It may not work as expected.

However, sometimes it may become handy to use it in loops, for example for delegating the recursivity of a func to a defer but this one is out of scope for this article.

1*_rdOI_SbL9m07QBcKMqz5Q.png
1*_T50bGcl0yf-ea62ALFwZw.png

Here, defer row.Close() calls inside the loop don’t get executed until the func ends — not when each step of for loop ends. All calls here will eat up the func’s stack and may cause unforeseen problems.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Solution #1:

Call it directly without defer.

1*JsvdjJRzGGmEiU1QvPyx-w.png
1*_rdOI_SbL9m07QBcKMqz5Q.png

Solution #2:

Delegate the work to another func and use defer there. Here, the deferred func will run after each time the anonymous func ends.

1*vrzyszEw14nw6blktrImyA.png
1*_rdOI_SbL9m07QBcKMqz5Q.png

Bonus Point

👾 The benchmark code is here 👾

1*_rdOI_SbL9m07QBcKMqz5Q.png

1*FFqiVq-lvmbRxzT6gOCGlQ.png

For all examples above

1*-OQBs5b4u65NRQM8aFukAw.png

#3 — Defer as a wrapper

Sometimes you need to defer with closures to be more practical or some other reasons I cannot guess right now. For example, to open a database connection, then run some queries and at the end to ensure that it gets disconnected.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Example

type database struct{}func (db *database) connect() (disconnect func()) {
fmt.Println("connect") return func() {
fmt.Println("disconnect")
}
}

Let’s run it

db := &database{}
defer db.connect()

fmt.Println("query db...")

Output

query db...
connect

Why it doesn’t work?

It doesn’t disconnect and it connects at the end, which is a bug. Only thing that happened here is that connect() gets saved aside until the func ends and it doesn’t run.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Solution

func() {
db := &database{} close := db.connect()
defer close()

fmt.Println("query db...")
}

Now, db.connect() returns a func and we can use it with defer to disconnect from the database when the surrounding func ends.

Output

connect
query db...
disconnect
1*_rdOI_SbL9m07QBcKMqz5Q.png

Bad Practice:

Although it’s a bad practice to do this but I want to show you how you can do it without a variable. So, I hope you can see how defer and more generally how Go works.

func() {
db := &database{} defer db.connect()() ..
}

This code is technically almost the same as the above solution. Here, the first parenthesis is for connecting to the database (which happens immediately on defer db.connect()) and then the second parenthesis is for defer to run the disconnector func (the returned closure) when the surrounding func ends.

This happens because db.connect() creates a value which is a closure and then defer registers it. The value of db.connect() needs to be resolved first to be registered with defer. This is not directly related with defer however it may solve some of your gotchas you may have.

1*_rdOI_SbL9m07QBcKMqz5Q.png

1*FFqiVq-lvmbRxzT6gOCGlQ.png
1*-OQBs5b4u65NRQM8aFukAw.png

#4 — Defer in a block

You may expect that a deferred func will run after a block ends but it does not, it only executes after the containing func ends. This is also true for all blocks: For, switch, etc except func blocks as we saw in the previous gotchas.

Because: defer belongs to a func not to a block.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Example

func main() {  {
defer func() {
fmt.Println("block: defer runs")
}() fmt.Println("block: ends")
} fmt.Println("main: ends")}

Output

block: ends
main: ends
block: defer runs

Explanation

The deferred func above will only run when the func ends not when the deferred func’s surrounding block ends (the area inside curly braces containing the defer call). As seen in the example code, you can create separate blocks just using curly braces.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Another Solution

If you want to run defer in a block you can convert it to a func such as an anonymous func just like the solutions of gotcha #2.

func main() {
func() {
defer func() {
fmt.Println("func: defer runs")
}() fmt.Println("func: ends")
}() fmt.Println("main: ends")
}
1*_rdOI_SbL9m07QBcKMqz5Q.png

1*FFqiVq-lvmbRxzT6gOCGlQ.png
1*-OQBs5b4u65NRQM8aFukAw.png

#5 — Deferred method gotchas

You can also use methods with defer. However, there’s a quirk. Watch.

1*_rdOI_SbL9m07QBcKMqz5Q.png

Without pointers

type Car struct {
model string
}func (c Car) PrintModel() {
fmt.Println(c.model)
}func main() {
c := Car{model: "DeLorean DMC-12"} defer c.PrintModel() c.model = "Chevrolet Impala"
}

Output

DeLorean DMC-12
1*_rdOI_SbL9m07QBcKMqz5Q.png

With Pointers

func (c *Car) PrintModel() {
fmt.Println(c.model)
}

Output

Chevrolet Impala
1*_rdOI_SbL9m07QBcKMqz5Q.png

What’s going on?

1*pTk0c8mVLT-sHBASXKDkXw.png

Remember that the passed params to a deferred func are saved aside immediately without waiting for the deferred func to be run.

So, when a method with a value-receiver is used with defer, the receiver will be copied (in this case Car) at the time of registering and the changes to it wouldn’t be visible (Car.model). Because the receiver is also an input param and evaluated immediately to “DeLorean DMC-12” when it’s registered with the defer.

On the other hand, when the receiver is a pointer when it’s called with defer, a new pointer is created but the address it points to would be the same with the “c” pointer above. So, any changes to it would be reflected flawlessly.

1*_rdOI_SbL9m07QBcKMqz5Q.png

1*FFqiVq-lvmbRxzT6gOCGlQ.png
1*-OQBs5b4u65NRQM8aFukAw.png

Alright, that’s all for now. Thank you for reading so far.

Let’s stay in touch:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK