5 Gotchas of Defer in Go— Part I

5 Gotchas of Defer in Go — Part I

Protect yourself from basic defer gotchas.

:panda_face: This article is only for beginner to intermediate level Gophers, masters: “ close the tab please ”.

#1 — Deferred nil func

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

Example

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

Output

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

Why?

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.

#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.

Here, defer rows.Close() calls 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.

Solution #1:

Call it directly without defer.

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.

For all examples above

#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.

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.

Solution

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

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

Now the first time db.Connect is called with defer it runs immediately and returns a closure without waiting the parent func ends, and that closure will be saved aside until the surrounding func ends. That’s why: It connects first, then queries the database and then disconnects.

Output

connect
query db...
disconnect

#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.

Example

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

Output

func ends
block 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) .

#5 — Deferred method gotchas

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

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

With pointers

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

Output

Chevrolet Impala

What’s going on?

Remember that the passed params to a deferred func are saved aside immediately without waiting 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.

Btw, I’m tweeting this kind of puzzles, tips and tricks almost daily, follow me on twitter to get them if you like.

Some examples:

Allright, end comes. Next time I’ll add more defer gotchas in the upcoming articles — there are 15 more gotchas in my queue right now . If you have any ideas what should I put in my articles queue please send me your comments below.

:heartbeat: Share this post with your friends. Thank you! :heartbeat:

I’m also creating an online course for Go → Join to my newsletter

“ Let’s stay in touch weekly for new tutorials and tips “

Don’t stop now, learn more:

Go Defer Simplified with Practical Visuals

Learn about defer, multiple defers, deferred methods, deferred closures. blog.learngoprogramming.com

Lobsters稿源:Lobsters (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 后端存储 » 5 Gotchas of Defer in Go— Part I

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录