fixing the other go loop bug
Go 1.21 added experimental support for fixing the loop capture bug. For reasons it’s never really bothered me, but the other loop bug does bite me some times. This is the one where the loop values are values.
I want references but go doesn’t have references.
values
The problem is easy to see when you’re looking for it, and hard to see when you’re glossing over the code.
func resetScores(players []Player) {
for _, player := range players {
player.score = 0
}
}
Before and after “the” loop fix, this will dutifully set the player’s score to zero. But which player? An anonymous player that only exists in the loop. The array will remain unchanged. Ugh.
hand fix
Fixing this requires slightly rewriting the loop.
func resetScores(players []Player) {
for i := range players {
players[i].score = 0
}
}
This looks kinda weird I think, and it’s not my first instinct to write it this way. I really just want a way to say hi, I’m iterating over the elements of this array, give me a reference to them.
mechanical fix
So let’s add references to go.
func resetScores(players []Player) {
for _, &player := range players {
player.score = 0
}
}
This can be translated by powerful state of the art tools into the go you’d have to write otherwise.
func resetScores(players []Player) {
for _i, _ := range players {
player := &players[_i]
player.score = 0
}
}
oh well
What’s annoying is the above broken form works if the array contains pointers. A lot of my code uses pointers, so I get used to writing the first form, and then it breaks when I do something different. Or I decide to optimize the code and reduce allocations by switching from []*Player
to []Player
without a careful audit of the impacted loops. Alas, I probably wouldn’t think to use the reference form even if available until it’s too late.
The common mistakes wiki lists the capture bug twice, but doesn’t mention this bug.
Tagged: go