Live data from Hacker News

More Gotchas of Defer in Go, Part II

blog.learngoprogramming.com

31–40 of 55 posts

Re: More Gotchas of Defer in Go, Part II

#31

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.

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

"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).

Re: More Gotchas of Defer in Go, Part II

#32
post #2

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.

Check out the first part too: https://blog.learngoprogramming.com/gotchas-of-defer-in-go-1...

I fell to these when I was learning Go for the first time and then I've seen many people also did so. There is no rule like these are gotchas for everyone.

Re: More Gotchas of Defer in Go, Part II

#33

Earlier quoted context omitted.

It's not terribly uncommon for larger functions to do something like func someSQLStuff() { tx, err := createTx() defer func() { if err != nil { tx.Rollback() log(err) } else { tx.Commit() } }() rows, err = tx.QueryContext( ... ) // more SQL } Basically, function-scoped cleanup. Like closing opened files.

But that defer is already lexically at the function scope, so block-scoped defer would do the same thing.

Ah, sorry, you meant function-scoped vs block-scoped not just in general. Yeah, agreed.

Re: More Gotchas of Defer in Go, Part II

#34
post #27
post #8

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

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…

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.

Re: More Gotchas of Defer in Go, Part II

#35
post #2

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.

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.

Re: More Gotchas of Defer in Go, Part II

#36
post #5

#4 can also be fixed with: for i := 0; i

Crikey! You wouldn't wanna allocate closures in a loop anyway, would you? Even if the compiler is smart about it. Doesn't read so well either.. what's wrong with adding a simple argument to your anonymous-func allocated outside of a loop? It's readable, and there are no 'gotchas'. The args are evaluated at the point of defer/go, not func execution. Simples.

Shadowing bites you sooner or later if it becomes a habit and gains you almost nothing in real terms. I keep running into subtle nasties in coworkers' commits from quick'n'dirty-that-stayed "convenience" shadowings. Need I say `err`.. of course linters help, but aren't a given in a "bring-your-own chosen dev-env" team. =)

Re: More Gotchas of Defer in Go, Part II

#37
post #27

Earlier quoted context omitted.

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…

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.

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.

Re: More Gotchas of Defer in Go, Part II

#38

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.

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

The tracing GC certainly complicates things, but you can make it work with a "scope" keyword like in D.

Re: More Gotchas of Defer in Go, Part II

#39

Earlier quoted context omitted.

Well I think the function scope the most useful, but wish there was block scope available too. So what I really wish was that languages used dot notation to go up scope eg ..name is that name two blocks out. This was one of the good parts of VB syntax which I miss in other languages. Think how much nicer it is than python's nonlocal and global, for example!

What's the use case for function scoped defer? I have never once needed function scoped RAII in C++ or any other language.

I have a comment at a higher level with a broader example, but for Go at least this is somewhat common:

    func f(i interface{}) {
      if closable, ok := i.(closable); ok {
        defer closable.close()
      }
      // do stuff with i, maybe other casts, etc
    }
There aren't many nice options for "if I can call X, defer a call to X" aside from shoving it into an `if`, where it'd be captured by that scope. I mean, you could do something like

    deferrable := func(){}
    if closable {
      deferrable = closable.close
    }
    defer deferrable()
but imagine doing that every time. It'd work, sure, but it'd also be more annoying.

Re: More Gotchas of Defer in Go, Part II

#40
post #39

Earlier quoted context omitted.

What's the use case for function scoped defer? I have never once needed function scoped RAII in C++ or any other language.

I have a comment at a higher level with a broader example, but for Go at least this is somewhat common: func f(i interface{}) { if closable, ok := i.(closable); ok { defer closable.close() } // do stuff with i, maybe other casts, etc } There aren't many nice options for "if I can call X, defer a call to X" aside from shoving it into an `if`, where it'd be captured by that scope. I mean, you could do something like de…

Couldn't you do:

    func closeIfNecessary(object interface{}) {
        closable, needsClosing := object.(closable)
        if needsClosing {
            closable.close()
        }
    }
And then just do:

    func f(i interface{}) {
        defer closeIfNecessary(i)
        ...
    }
Doing it this way also saves you boilerplate by factoring the downcast out into a separate function.
Post reply on HN