What!? How are all 10 items in my array the exact same thing!?!?

The argument that I'm making is that by adopting a simple habit through all of your code, you get the upside of never experiencing a strange language quirk and really no downsides - other than typing an extra line.

Totally worth it.

Instead of ever doing this:

for _, x := range things {
    // ...
}

Always do this:

for i := range things {
    x := things[i]
    // ...
}

If you can stick to that, you'll never have to scratch your head over debug statements that look like this:

Appending "Sprocket"
Appending "Slinky"
Element 0 "Gizmo"
Element 1 "Gizmo"

Problem: Range Elements

Let's say that you're hitting a JSON API to retrieve a list of widgets and for some reason - perhaps to filter the list - you make a copy of the elements while rangeing over them.

What could possibly go wrong?

Let's consider a very simple program that filters a list of Widgets:

package main

import (
    "fmt"
    "strings"
)

type Widget struct {
    Name string
}

func main() {
    // As if retrieved from a JSON API
    ws := []Widget{
        Widget{Name: "Sprocket"},
        Widget{Name: "Gizmo"},
    }

    // Filtering the list as if for a search
    search := "S"
    results := []*Widget{}
    for _, w := range ws {
        if strings.HasPrefix(w.Name, search) {
            fmt.Println("Added", w.Name)
            results = append(results, &w)
        }
    }

    // Let's see what we get!
    for _, w := range results {
        fmt.Println("Got", w.Name)
    }
}

And the result?

Added Sprocket
Got Gizmo

What!?!?

Well, as it turns out, range re-uses the same memory pointer in the stack for each iteration of the loop (for efficiency or some such I presume), so you'll end up with all of your pointers being to the very last item in the array.

This is even easier to observe if we just add another "S" item to our list and re-run our program:

ws := []Widget{
    Widget{Name: "Sprocket"},
    Widget{Name: "Slinky"},
    Widget{Name: "Gizmo"},
}

Now we get this:

Added Sprocket
Added Slinky
Got Gizmo
Got Gizmo

Solution: Only Use Index

Like I said, as a general pattern, just stick to this:

for i := range things {
    x := things[i]
    // ...
}

Counterpoint:

It is possible that, in very very specific circumstances, you may profile an application that has a very rare loop where the performance of allocating memory changes to a non-trivial degree by using the pre-allocated element. I haven't personally encountered such a case in Go (though I have seen it elsewhere), but I believe it could exist. However, if that happens, you should probably switch to Rust for that part of your application - you'll get bare metal performance control without giving up the appearance and pleasure of a high-level language.


By AJ ONeal

If you loved this and want more like it, sign up!


Did I make your day?
Buy me a coffeeBuy me a coffee  

(you can learn about the bigger picture I'm working towards on my patreon page )



Published

2019-6-28



Buy me a coffeeBuy me a coffee


  73% of Goal Reached


Want more like this?