6 ms·
WTF. I haven't been interested in Go after looking at it briefly a decade ago. This post brings back the weird feeling about it that I had then. It looks like t
by jstimpfle 4y ago
WTF. I haven't been interested in Go after looking at it briefly a decade ago. This post brings back the weird feeling about it that I had then. It looks like taking the address of a local variable (item := item; append(..., &item);) and using that outside the scope of the local variable. But apparently that is ok.
Variations of such perceived weirdness exist in many other languages with complicated "object models" as well to be fair. Delphi has some strange adressing stuff going on as well. Python has this weird "default list" thing. Most object languages don't let you take the address of something (like the Go example shows) at all, but have only object references which I find unergonomic.
- silon42 4y agoI also feel there is a problem in that line, not necessarily just a for loop. Maybe that's my C/C++/Rust experience.
- tialaramex 4y agoIn Rust we know we only gave the append function an immutable reference which lives until the next iteration. If append is OK with that, we're golden. Maybe internally it clones these references -shrug. If append() needs the thing, not just an immutable reference which expires soon, its signature would demand we move one into it, and we don't have one so we'd need to e.g. make one with Clone.
- maccard 4y ago> Maybe internally it clones these references -shrug. That's what go does under the hood in 95% of cases. If I do: func foo(a int) *int { return &a } func main() { fmt.Println(*foo(3)) } It does the right thing. It's just that in this case (and in lambdas) they got it wrong.
- masklinn 4y agoTBF Rust also intentionally used the semantics Go is migrating to from the start, because: - experience with the issue in languages with wider scoping e.g. it’s a common issue in JS, as well as Python (though slightly less so) - Rust’s iterators were originally internal so that was pretty natural (1) is also why for(let and for(const have different scoping than for(var in JS: `var` has function scoping, `let` and `const` were introduced with block scoping, and for loops they were specifically specced with “inner” (per-iteration) scope.
- Blikkentrekker 4y agoAside from Python and the Shell, it never occurred to me that any language could possibly think of other semantics. It's news to me that C++ also assigns rather than initializes on each iteration. It's simply a very bad idea that provides no use yet creates many bugs.
- masklinn 4y agoThe thing is it only create many bugs when you throw closures into the mix. So historically languages did it because it was easy to implement (create a counter, increment the counter, run the loop body). Before the early aughts, closures were mostly really common in functional languages which tend towards immutable bindings (and immutability in general), and very closure-focused languages closured everything so didn’t hit that issue (e.g. you wouldn’t hit it in Smalltalk because your counter would be a parameter to a block, so closing over that was no issue). It’s really in the 00s with the explosion of callbacks-pile-javascript (and more generally the functionalisation of imperative languages) that the problem became a serious concern: you loop over a thing, you start some sort of async operation (network request for instance), and you find out that despite the request being correct the entire thing goes wonky (then again things commonly went wonky which didn’t help).
- Blikkentrekker 4y ago> The thing is it only create many bugs when you throw closures into the mix. So historically languages did it because it was easy to implement (create a counter, increment the counter, run the loop body). It's a problem with references in general as this shows. I also don't feel it's easier to implement at all. One can either rewrite: for $id:var in $exp:iter { $code:body } to: { let $id:var; while(True) { let result = $exp:iter.next(); if(result.is_none()) break; $id:Var <- result.extract(); $code:body } } Or while(True) { let result = $exp:iter.next(); if(result.is_none()) break; let $id:var = result.extract(); $code:body } The latter implementation is as far as I see easier, not more complex. Obviously all the code to create scoping already exists in the compiler and for-loops over an iterator work with a syntactic rewrite to an infinite loop with a break.
- benhoyt 4y agoYes, taking the address of a local variable is exactly what it's doing. But Go's compiler (and garbage collector) ensures it's safe to do that. The compiler will allocate on the stack where possible, and the heap where necessary, invisible to you. This is all very normal in Go. I'm not sure prefixing your comment with "WTF" and your 10-years-ago dismissal helps the discussion here. Yes, as we've learned, this was probably the wrong decision, but it's not hard to see why it was done that way originally (C# made the same decision), and now they're having a reasonable technical discussion to try to solve it. And -- even though I've been bitten by this several times myself -- it's not a terribly common occurrence.
- jstimpfle 4y agoI apologize for being snarky. "WTF" is an expression of surprise though, much more than it is criticism. My point was that these object models achieve a desired level of user-friendliness and safety at the cost of being less orthogonal and less composable (as compared to, say, C) and having weird corner cases and surprises that catch you off-guard.
- jerf 4y agoIt can be argued, based on the number of people who at some point write code in C that takes the address of a stack variable and returns it back out of the function scope, that the "less orthogonal" corner case that catches you off guard is the way C forbids that action. Do not mistake internalized concepts from a particular language as some sort of divinely approved dictate of how programming must work. This was basically Dijkstra's point in his BASIC considered harmful post... I think in 2022 it should be C considered harmful for the same reason. C is not the base truth of computation. It isn't even very good. A language smart enough to analyze taking pointers and notice it can't put something on a stack and simply take care of it is, in my opinion, the one that is not catching you off guard... specifically, the "guard" that one must take in C around what is stack versus heap.
- deleted 4y ago[deleted]
- Cthulhu_ 4y agoI mean Javascript does / did the same thing (var loop variables get hoisted to the function scope, so they are available outside of the loop; add to that that `range` creates a pointer and you have a perfect storm of weird, confusing things. But this is an important point: They were aware of it; it was by design; they had the chance to change it before 1.0 and didn't, and now they are showing willingness to change. So many languages are resistant to change on the one hand (I mean Go is), yet not resistant to keep adding features (e.g. Java / JS).
- udp 4y ago> Javascript does / did the same thing (var loop variables get hoisted to the function scope, so they are available outside of the loop; add to that that `range` creates a pointer and you have a perfect storm of weird, confusing things. let is preferred nowadays in JS and doesn’t have the weird hoisting behaviour that var does/did. JS has neither “range” nor pointers though so I’m not sure what you mean by that.
- avl999 4y ago`&varName` is not equivalent to C version of the superficially similar looking operation.