5 More Gotchas of Defer in Go — Part II
Protect yourself from basic defer gotchas. For beginners.


We’re continuing our journey of defer gotchas.
Read Part I if you’ve missed it.
Protect yourself from basic defer gotchas.blog.learngoprogramming.com
If you don’t know anything about defer please read this starter article first.
#1 — Z to A
This can become a gotcha when you’re learning Go for the first time.

Example
func main() {
for i := 0; i < 4; i++ {
defer fmt.Print(i)
}
}Output
Go runtime saves deferred funcs in a stack which means that they work in reverse order. Read this article to understand it better.
3
2
1
0




#2—Scope ate my param
This is actually a scope gotcha, however, I wanted to show you how it behaves with defer and named result values.

Example
Let’s define a func which registers a deferred func to release the resource r in defer after the func returns.


Create a reader which returns an error on Close
type reader struct{}func (r reader) Close() error {
return errors.New("Close Error")
}Let’s try
reader always returns an error when its Close() method is called and release() will call it inside defer as seen in the image above.
r := reader{}err := release(r)
fmt.Print(err)
Output
nil
err is nil but we were expecting: “Close Error”.
Why is that?
Assignment inside the deferred func’s if block shadows the named result value err with a new err variable. So, release() returns the original err result-value.

Solution
We need to use the original err variable ofrelease(). To do that, look at the line #3 below, this code doesn’t declare a new err variable, instead, it uses the original err result value (with = instead of :=). This solves the shadowing problem.


= and := check out this article.



#3—Quick to run the params
Params passed to a deferred func are evaluated when the defer is registered not when it runs. Check out also this example in my other article.

Example
type message struct {
content string
}func (p *message) set(c string) {
p.content = c
}func (p *message) print() string {
return p.content
}Let’s try it
func() {
m := &message{content: "Hello"}defer fmt.Print(m.print())
m.set("World")// deferred func runs
}
Output
"Hello"

Why the output is not “World”?
Here in defer, fmt.Print is deferred until the func returns but m.print() is evaluated immediately. So, m.print() will return “Hello” and it’s saved aside until the surrounding func returns.



#4—Captured in a Loop
A deferred func will see the most recent values of the surrounding variables when it runs — except the passed params to it. Let’s see how it happens inside a loop.

Example
Let’s defer a closure inside a loop.
for i := 0; i < 3; i++ {
defer func() {
fmt.Println(i)
}()
}Output
3
3
3
Why?
When the code runs, the deferred func sees the recent value of i. That’s because, when the defers are registered, Go runtime captures the address of i. After the for loop ends i becomes 3. So, when the defers run, all of them will look to the same i which is 3 (its recent value after the loop ends).



Solution
Pass parameters directly to defer.
for i := 0; i < 3; i++ {
defer func(i int) {
fmt.Println(i)
}(i)
}Output
2
1
0
Why it works?
Because, here, Go runtime creates different i variables and save them aside with the correct values. Now, defers don’t see the original i variable of the loop anymore, they see their own i local variables.
Solution #2
Here, we’re intentionally shadowing i with a new i inside the scope of the for loop. I‘m not a fan of this style. So, I’ve added this here after a discussion in Hacker News.
for i := 0; i < 3; i++ {
i := i
defer func() {
fmt.Println(i)
}()
}Solution #3
If there’s only one func call, you can use it directly.
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}

#5 — Point of no return?
Returning values from a deferred func practically has no effect on the caller. However, you can still use named result values to change the result values.

Example
func release() error {
defer func() error {
return errors.New("error")
}()return nil
}
Output
nil

Solution
Defer can change the named result values.
func release() (err error) {
defer func() {
err = errors.New("error")
}()return nil
}
Output
"error"
Why it works?
Here, we’re assigning a new value to release func’s err result value inside the defer. And, the func returns that value. The defer doesn’t directly return a value here, it helps returning the value.

Tip
You don’t need to use defer everywhere. You can also just return the error directly without defer.
Defers are more handy when there are multiple return paths and you want to catch them all in a single place. Better to think more about how you can simplify your code further.


🔥 I’m also creating an online course for Go: Join to my newsletter
“ Let’s stay in touch weekly for new tutorials and tips “

Btw, I tweet puzzles, tips and tricks almost daily, follow me on twitter to get them if you like.
Some examples:



Don’t stop now, learn more:
Part 3 of this article series is also here:
Gotchas and tricks about defer.blog.learngoprogramming.com
Functions:
An overview about: Anonymous, higher-order, closures, concurrent, deferred, variadic, methods, interface and other…blog.learngoprogramming.com
Easily understand Go funcs with visuals.blog.learngoprogramming.com
Learn everything about Golang variadic funcs visually with common usage patterns.blog.learngoprogramming.com
Enums:
Golang Enums & iota Guide—Full of tips and tricks with visuals and runnable code examples.blog.learngoprogramming.com
More?

💓 Share this post with your friends. Thank you!!! 💓



I’m also creating an online course for Go → Join to my newsletter
“ Let’s stay in touch weekly for new tutorials and tips “