5 ms·
I still don't understand why defer works on function scope, and not lexical scope, and nobody has been able to explain to me the reason for it. In fact this wa
by torginus 1y ago
I still don't understand why defer works on function scope, and not lexical scope, and nobody has been able to explain to me the reason for it.
In fact this was so surprising to me is that I only found out about it when I wrote code that processed files in a loop, and it started crashing once the list of files got too big, because defer didnt close the handles until the function returned.
When I asked some other Go programmers, they told me to wrap the loop body in an anonymus func and invoke that.
Other than that (and some other niggles), I find Go a pleasant, compact language, with an efficient syntax, that kind of doesn't really encourage people trying to be cute. I started my Go journey rewriting a fairly substantial C# project, and was surprised to learn that despite it having like 10% of the features of C#, the code ended up being smaller. It also encourages performant defaults, like not forcing GC allocation at every turn, very good and built-in support for codegen for stuff like serialization, and no insistence to 'eat the world' like C# does with stuff like ORMs that showcase you can write C# instead of SQL for RDBMS and doing GRPC by annotating C# objects. In Go, you do SQL by writing SQL, and you od GRPC by writing protobuf specs.
- grey-area 1y agoThere’s probably no deep reason, does it matter much?
- torginus 1y agoYes it does, function-scope defer needs a dynamic data structure to keep track of pending defers, so its not zero cost. It can be also a source of bugs where you hang onto something for longer than intended - considering there's no indication of something that might block in Go, you can acquire a mutex, defer the release, and be surprised when some function call ends up blocking, and your whole program hangs for a second.
- nasretdinov 1y agoI think it's only a real issue when you're coming from a language that has different rules. Block-scoping (and thus not being able to e.g. conditionally remove a temp file at the end of a function) would be equally surprising for someone coming from Go. But I do definitely agree that the dynamic nature of defer and it not being block-scoped is probably not the best
- wtetzner 1y agoHaving to wrap a loop body in a function that's immediately invoked seems like it would make the code harder to read. Especially for a language that prides itself on being "simple" and "straightforward".
- christophilus 1y agoI’ve worked with languages that have both, and find myself wishing I could have function-level defer inside conditionals when I use the block-level languages.
- gwd 1y agoSo sometimes you want it lexical scope, and sometimes function scope; For example, maybe you open a bunch of files in a loop and need them all open for the rest of the function. Right now it's function scope; if you need it lexical scope, you can wrap it in a function. Suppose it were lexical scope and you needed it function scope. Then what do you do?
- gf000 1y agoMaking it lexical scope would make both of these solvable, and would be clear for anyone reading it. You can just introduce a new scope wherever you want with {} in sane languages, to control the required behavior as you wish.
- lowmagnet 1y agoYou can start a new scope with `{}` in go. If I have a bunch of temp vars I'll declare the final result outside the braces and then do the work inside. But lately days I'll just write a function. It's clearer and easier to test.
- tgv 1y agoCurrently, you can write if (some condition) { defer x() } When it's lexically scoped, you'd need to add some variable. Not that that happens a lot, but a lexically scoped defer isnt needed often either.
- iainmerrick 1y agoWhat's an example of where you'd need to do that? I can't recall ever needing that (but that might just be because I'm used to lexical scoping for defer-type constructs / RAII).
- tgv 1y agoSomeone already replied, but in general when you conditionally acquire a resource, but continue on failing. E.g., if you manage to acquire it, defer the Close() call, otherwise try to get another resource. Another example I found in my code is a conditional lock. The code runs through a list of objects it might have to update (note: it is only called in one thread). As an optimization, it doesn't acquire a lock on the list until it finds an object that has to be changed. That allows other threads to use/lock that list in the meantime instead of waiting until the list scan has finished. I now realize I could have used an RWLock...
- cabirum 1y agoLexical scope does not have a stack to put defer onto.
- masklinn 1y agoAll the defer sites in a lexical scope are static, you can target those sites directly or add a fixed-size stack in the frame.
- __s 1y ago1. it avoids a level of indentation until you wrap it in a function 2. mechanic is tied to call stack / stack unwinding 3. it feels natural when you're coming from C with `goto fail` (yes it annoys me when I want to defer in a loop & now that loop body needs to be a function)
- torginus 1y agoI think you hit the nail on the head - I think it's the stupid decision on Go lang designers part to make panic-s recover-able. This necessitates stack unwinding, meaning defer-s still need to run if a panic happens down the stack. Since they didn't want to have a 'proper' RAII unwinding mechanism, this is the crappy compromise they came up with.
- jayd16 1y agoYou can write SQL or use protbuf spec with C#. You just also have the other options.
- freeopinion 1y agoAs it is, you can have it both ways. Wrap the body in a function if that's what you want. Don't wrap to get wider scope.