
More Gotchas of Defer in Go, Part II - inancgumus
https://blog.learngoprogramming.com/5-gotchas-of-defer-in-go-golang-part-ii-cc550f6ad9aa
======
jitl
Also read the official blog post on defer: [https://blog.golang.org/defer-
panic-and-recover](https://blog.golang.org/defer-panic-and-recover)

It covers about half of these gotchas by laying out how the defer statement
works in plain English. The other half (like how closures work inside loops in
Go) are covered elsewhere in the language tour.

I do really like the visualizations! Makes it very clear how these mechanics
work.

~~~
jerf
The content is good, and it is accurate, but even though defer can be a bit
tricky this blog post series seems to literally be describing the entire
semantics of "defer" as "gotchas". It's not _that_ tricky!

~~~
inancgumus
Well, it depends. Upgoing posts will go deeper on defer.

------
brabel
Every point mentioned was not a gotcha to anyone who read the official
introductory tour of the language. The behaviour may be unexpected to newbies,
but if you spent an hour or so learning the language, you shouldn't be falling
for these.

~~~
bsaul
Completely agree. This is probably the most comprehensible list of programming
language « gotchas » i’ve ever read. It feels really a confirmation that the
language authors have succeeded in their goal for simplicity.

------
barell
I think most of the people who understand the basic principles of how
computers (and many languages) work wouldn’t be surprised by any of the
„gotcha” described in this article. It seems to be written for people who just
started working with golang without any or little knowledge about information
technology.

~~~
readittwice
I am not sure about this. Swift for example also has `defer` statements but
its behavior differs from Go's. Swift executes defer statements at the end of
the block, not the end of the function. And AFAICR Swift doesn't evaluate the
parameters right away like Go does. If you have already read how defer works
in detail in Go, you probably already know this. Devs new to the language or
those that haven't used defer in those cases might still be surprised when
this shows up.

------
brian-armstrong
Defers just feel like a watered down version of what you get with good scoping
and RAII. They're a half measure for something programming languages solved
decades ago.

~~~
mratzloff
Go has a garbage collector. RAII is only viable in languages where destruction
is predictable, like C++ or PHP.

~~~
int_19h
"defer" is effectively used for predictable destruction in Go, and a more
automated RAII scheme could be used in its place.

C++/CLI is a language that targets a garbage-collected runtime, yet has full-
fledged RAII semantics (they're mapped to CLR Dispose pattern).

~~~
mratzloff
Oh, I totally forgot about C++/CLI! You're absolutely right; that's my
mistake. I do agree that RAII is preferable.

------
knorker
#4 can also be fixed with:

    
    
        for i := 0; i < 10; i++ {
          i := i  // Creates a *new* `i`
          defer func() { … something with i … }
          go func() { … something with i … }
        }

~~~
poorform
This is too sneaky and seems like poor form.

I'm curious what good use-case there is for this, that the language bothered
to support and allow this to even compile.

Duplicate variable name declaration + referencing in the same scope is
unintuitive at best, and just seems _wrong_.

~~~
knorker
I don't feel that it's in more poor form than the solution suggested in the
article:

    
    
        for i := 0; i < 3; i++ {
          defer func(i int) {
           fmt.Println(i)
          }(i)
        }
    

This shadows `i` pretty much the same amount as what I wrote. If `i` gets a
different name inside the lambda, then it'd also be worse because then you
could accidentally use `i` still.

~~~
tylersmith
I personally prefer the version presented in the article, but your version has
at least one nice feature in that you can cleanly specify at the top of the
loop body which variables are being shadowed.

------
ramenmeal
I code in go every day professionally. These are not gotchas, they are well
defined behaviors.

~~~
inancgumus
Check out the first part too. Btw, gotcha means what you say: "a gotcha ...
works as documented but is counter-intuitive and almost invites mistakes ..."
[https://en.wikipedia.org/wiki/Gotcha_(programming)](https://en.wikipedia.org/wiki/Gotcha_\(programming\))

------
br1
It's indefensible that defer works on the function and not on the scope.

~~~
Groxx
Both seem like fair options to me. With function scope, you can

    
    
        func f() {
          x := ...
          if x.something() {
            x.doSomethingEarlier()
            defer x.cleanup()
          }
          // use x however you like
        }
    

where scope-based forces you to do stuff like

    
    
        func f() {
          x := ...
          if x.something() {
            x.doSomethingEarlier()
            defer x.cleanup()
            // use x however you like
          } else {
            // use x however you like
          }
        }
    

In a scope-based defer, you'd have to keep all related code in the scope,
nesting it another layer deeper / possibly duplicating it.

On the flip-side is of course that this doesn't work like most would probably
want in function-scoped:

    
    
        for i := 0; i < 4; i++ {
          x := get(i)
          defer x.cleanup()
          x.whatever()
        }
    

and you're forced to

    
    
        for i := 0; i < 4; i++ {
          x := get(i)
          func() {
            defer x.cleanup()
            x.whatever()
          }()
        }
    

I've seen both of these patterns pretty frequently, in Go and in other
languages. Go could, of course, have both a func_defer and a scope_defer, but
that doesn't seem like it'd fit with the fairly strong focus on keeping the
language feeling small and simple. So they had to pick one, and it can't
handle both cases.

~~~
infogulch
I think function scope is a good default, and wrapping in an anonymous
function and calling it (like your last example) is a simple workaround to get
the scope_defer behavior. If it was scope based there's nothing you could do
to get func_defer behavior.

~~~
Groxx
Yeah, I generally feel the same way. For fairly simple use, scope is more
consistent (all scopes / closures are identical), but func is a bit more
flexible if you're willing to pay with simple boilerplate.

I mean, you can convert them into each other. Scoped can do something like
this (go+python blended code 'cuz lazy):

    
    
        func f(){
          deferred := []
          defer func() { for d in deferred.reverse(): d() }() // plus error handling
          if x.something() {
            deferred.push(func(){ cleanup() });
          }
          // same as func scope
        }
    

but that's a bit more ridiculous / error-prone (though a helper func is
obviously possible) than the equivalent IIFE for func -> scope. More explicit,
I suppose, but bleh.

~~~
pcwalton
It's more explicit, which is a _good_ thing, as it makes the intent clear.
This matters if, for example, the function is later refactored to inline into
a caller.

~~~
Groxx
It also allows more flexibility (do you execute them in the order they were
enqueued, or in reverse?), more room for errors, confusion between different
patterns / lack of consistency across different codebases, etc.

Explicit-all-the-things isn't an unambiguous Good Thing™. If it were, we
wouldn't even be discussing this - it's an abstraction, which is less explicit
than e.g. building defer out of a list and using GOTO.

