Live data from Hacker News

More Gotchas of Defer in Go, Part II

blog.learngoprogramming.com

41–50 of 55 posts

Re: More Gotchas of Defer in Go, Part II

#41
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've never seen the first pattern in any other language. Aside from downcasts as in your other example, why would you only want to clean up an object if some condition is true? The 99% use case of defer is for resource destruction, which you nearly always want to do in the same scope the object was initialized in (and that observation in fact is what underlies RAII).

Re: More Gotchas of Defer in Go, Part II

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

> If it was scope based there's nothing you could do to get func_defer behavior.

That's clearly false. You could set up a list to hold objects to be disposed (or, more generally, closures to execute) and defer a simple procedure that disposes of all objects in the list. This is in fact what the implementation of defer must do internally.

Re: More Gotchas of Defer in Go, Part II

#43
post #37

Earlier quoted context omitted.

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 handlin…

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.

Re: More Gotchas of Defer in Go, Part II

#44
post #39

Earlier quoted context omitted.

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.

As long as you can always call .close() regardless of the code below, yep - that'd work, and is definitely more readable.

If you can't call it unless [some other conditions], it goes back to the same kind of problem though. "closable" may not be a good choice on my part, as they're often called unconditionally.

Re: More Gotchas of Defer in Go, Part II

#45
post #37

Earlier quoted context omitted.

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 handlin…

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.

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.

Re: More Gotchas of Defer in Go, Part II

#46

Earlier quoted context omitted.

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.

> If it was scope based there's nothing you could do to get func_defer behavior. That's clearly false. You could set up a list to hold objects to be disposed (or, more generally, closures to execute) and defer a simple procedure that disposes of all objects in the list. This is in fact what the implementation of defer must do internally.

I think by that he means it wouldn't be possible to get func_defer behavior in the example with a single keyword, or without some form of qualifier. The compiler wouldn't be able to differentiate the behavior.

Re: More Gotchas of Defer in Go, Part II

#48
post #31

Earlier quoted context omitted.

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

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

Re: More Gotchas of Defer in Go, Part II

#49

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.

PHP also has a garbage collector.

Swift and D also have garbage collectors and support RAII.

And since I already see it coming, reference counting is a garbage collection implementation algorithm in computer science literature.

Re: More Gotchas of Defer in Go, Part II

#50
post #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 an…

> Crikey! You wouldn't wanna allocate closures in a loop anyway, would you?

Very often yes I do.

Go encourages synchronous APIs, making it up to the caller to add concurrency. This is great, in my opinion. E.g. this is a common pattern:

  var wg sync.WaitGroup
  ch := make(chan int, len(items))
  for _, item := range items {
    item := item
    wg.Add(1)
    go func() {
      defer wg.Done()
      ch 
Similar patterns with defer, although yes I'd say less often in a loop. Though in order to be "exception safe" (panic safe) I often do:

  foo, err := openFileOrSomething()
  defer foo.Close()
  [… do something …]
  err := foo.Close()
So that even if "do something" throws exception… err… panics… the file gets closed. And double-closing is safe. That's not a closure though, in this example. So maybe not so good.
Post reply on HN