Yeah, I am currently learning Go as well and I ran exactly into this problem yesterday and it took me almost 2 hours to solve it. Good no-one is going to experience it any more.
this is a pretty chill thing to be honest. I am not very impressed or certainly happy with this fix but yeah I am just biased because recognising and catching this potential bug is on my muscle memory. But yeah, I can see how this bug could have been a mess to deal with by a person newly getting into Go.
There is definitely somebody on the Go team saying "told you so" right about now. You should interview this chad and see what other ideas he wants to fix
if err := Something(); err != nil { ... } blah, err := foo() Hopefully. The first case should just be: if err := Something() { ... } The second, conflicted. I think we can have another := that returns default values and the error if it fails. blah ?= foo() Translates to: blah, err := foo() if err != nil { return ..., err }
@@advertslaxxor I want an or keyword for this : err := Something() or return err err := Something() or { log.Error("...", err) return err } Or, better yet, automatic capture of the first error-type return value or the first returned value if no error type given: Something() or return error
I was complaining about that back in February 2010, that there was no reasonable code which would depend on the original behavior, but that the feature was a pure footgun. Finally they fixed it. 🎉
yeah. still so many things unfixed since forever. I was really excited for Go at the beginning. not so much anymore after I've seen many stupid design choices and weird things in the stdlib.
This takes me back to 2012, which is when C# solved this exact problem by copying the loop variable behind the scenes. Java got closures in 2014, and right from the start it's been a compiler error if you didn't copy the loop variable before capturing.
This was a rollercoaster of emotions for me. At first, I PASSIONATELY thought the same - this is a skill issue. And then I had the gottem moment. 10/10 move from Go. Gotta say though, you feel pretty big brained knowing this, and telling some junior that tHe sCoPe oF a VaRiAbLe iS tHe LoOp, and bestowing that knowledge in a PR review...but getting caught in that mess is not certainly not worth it. I only hope that if there are performance implications like GC on the iteration of the loop or something, and that if there are, compiler is clever enough to use the old method if no references are passed around
I haven't used Go, but I was surprised this was even a thing, considering this had been a known issue in JavaScript for years, and JavaScript fixed it in 2016 with let/const.
I can’t imagine so many people in the comments saying that they had never encountered this issue. I ran into the problem the first week I learned Go. Back then I just thought that Golang generally didn’t support scoped variables in condition statements and I had to use IIFE to avoid the problem. Glad that they admitted that it was an issue and tried to fix.
This reminds me of a change they made in Julia 1.0 where `x = 0; for i in 1:10 x += i end` would leave you with x==0 (instead of the expected x==55) unless you modified it to `global x = i` (otherwise inside the for loop was a new scope and a new local x). After many many complaints they later decided that you'd get the expected behaviour in the REPL, but when called from a script it would give you a warning. Just to make this even more confusing, if you wrap that code in a function, you get the expected behaviour again, which is a real hassle if you're prototyping the internals of a function in global scope. Ah Julia, love the language but that is such a horrible surprise for people new to it.
this is an excellent change and how all languages should do it. this is how humans would intuitively expect it to work anyways and there is really no use case where you would need the old behaviour
It's night and day diff when a language is being managed by experts who know wtf they're doing and not just shoving the language with shiny things. That's why I believe in Go's future, competency is beautiful isn't it.
This is literally the exact same issue that JavaScript had to fix. So basically Go is created by people just as expert as the people that made JavaScript.
I'm ashamed to admit that I've never considered what the scope of a "foreach" variable is. I certainly didn't consciously consider it to be the same as the indexer of a typical "for" loop. Now I am a tiny bit wiser.
I think I ran into such a bug yesterday, in C. It introduced really weird errors, segmentation faults before main starts. I should try go a little because I don't understand this code at any level. Are "func" lambdas and [] fat pointers? Is
No, the extra copy is optimized away. You can see that this is true if you enter your example into godbolt. (RU-vid won’t let me paste a link, I think, but you can Google it.) Notably, no extra copying occurs even when the variable is captured for use in a goroutine. I’d be happy to try and explain the assembly if you’re unfamiliar btw, I know it can be dense!
@@anon_y_mousse I meant that the second copy is optimized away - that there would not be a copy both from the slice to v and then from v to vcopy. In my opinion a single copy is completely reasonable. I would expect the loop variable to behave like v := array[i], not v := &array[i]. The issue you’re concerned about isn’t one of copies but one of lifetime. Is the lifetime of the loop variable considered to be the lifetime of the entire loop or a single iteration? This does not affect performance as you seem to claim, but only the semantics of which memory locations will be accessed when the variable is captured by another function.
code coverage doesn't say anything about quality of coverage. it is useful to tell you what you're entirely missing, and that's about it. more a floor than a ceiling
"...even the expert of experts at Go can't write it correctly". That was epic! What is the cost of a bug in production? Maybe dealing with Rust's difficult mental model will be worth it in the end, who knows.
It only matters if the lifetime of the variable escapes the loop, such as in the goroutine examples. In those cases there will be more GC, yes, but you wanted that anyway because you wanted each goroutine to have its own variables.
"go is becoming a really good modern language" - true. my only gripe is: why did they have to repeat all the stupid mistakes from other languages first? things like generics and package management existed way before golang. They could have just started with that right off the bat. Same goes for proper error handling, logging, flag parsing, and being able to return an effing return code from main().
And what if I don't use go routines and don't want to modify the element of slice, but just use it as a source of values? With the old compiler I'd write: type record struct { Name string Age int } func main() { ages := []int{15, 20, 25, 40} names := []string{"John", "Mary", "Peter", "Sophie"} records := make([]record, len(names)) for i := range names { records[i].Age = ages[i] records[i].Name = names[i] } for _, rec := range records { fmt.Println(rec) } } and the iteration vars 'i' and 'rec' were reused in each iteration. Now they get saved in each iteration. This is BAD. In the best scenario for the second loop, the new compiler instructs the runtime to save only index iteration var and to use it as a reference to the element in the array. But this can still be 100 milion of ints saved per loop, if the slice is that long.
All of this is starting to look like perl and other languages had figured out 18 years ago. It amazes me that the same problems in everyone's new features and newer programming languages keep repeating the same mistake over and over again. After I punted Rust, I am back into C++ crazy template stuff. Same mistakes in all language designs.
This indeed seemed like pretty bad design from Go originally. I honestly prefer writing out indexed for look though and many people not understand why: why? Because in all random languages always harder to fuck it up - both perf-wise and semantics-wise and also known to everyone from first sight.
Glad to see this get addressed. The reason why this is such the pain is that Go doesn't have Optional type. We need to use things like *int to represent Optional type so it become abundant in the code and hard to catch on the fly.
array = [1, 2, 3] for i in array: list.add(closure that uses i) closures in the list == (3, 3, 3) You expect to see different i for each item in array, but clojures close over variables and not values. So every closure you added to the list - remembers the i variable, and not the value. How go fixed this: now address of 'i' is unique per iteration, not per loop, so every closure will see the unique variable, that won't change with next iteration.
@@theodorealenas3171 because it is unique behaviour of closures. They never explicitly copy values, closures keep the reference to the address of variable. That is practically the definition of closure.
As I said in more than one video, I thing go is just bad language. And that such crap can happen, is just another stone in their garden. Well eventually in like 10 years they might become decent, just look at PHP, it's now IMO one of the better languages at least by DX. PHPUnit for testing is hands down best thing I actually used for testing, symfony debug stuff is quite ahead of say nextjs. Exceptoins and fail fast is super nice when you get good stack traces. As opposed to errors that provide no traces, or god forbid you missed that function returns error, and it occurs, you are in for some real fun time.
golang is such a badly designed language. Now fix generics, interfaces, null pointers, add immutable types, add pattern matching with exhaustiveness checks, proper error handling, and then maybe it can be worth looking into
@@annoorange123 Being able to compose/chain functions that return errors easily. Having errors that are not strings. Errors that are not easily ignored by mistake.