I'm seeing different behavior in my program that's tied to this particular loop in my program but I'm not sure I understand why it's behaving the way it is.
//global variable
var cmds = []string {
"create",
"delete",
"update",
}
func loop1() {
actions := make(map[string]func())
for _, cmd := range cmds {
actions[cmd] = func() {
fmt.Println(cmd)
}
}
for _, action := range actions {
action()
}
}
func loop2() {
actions := make(map[string]func())
for i, cmd := range cmds {
command := cmds[i]
actions[cmd] = func() {
fmt.Println(command)
}
}
for _, action := range actions {
action()
}
}
The output for loop1()
is
update
update
update
The output for loop2()
is
delete
update
create
I went looking on the internet and read the following
When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index
It says a copy, so does that mean it returns a copy of the string but it's really a pointer to variable cmd
? In which case any references to cmd
will by the end of the loop all actually reference the last element in the array, e.g. update
? Does this mean that elements of an array should always be referenced by their index when using the range
method, and what's the use case for using the element it returns since it's always updating the pointer?
The problem with
loop1()
is that you store a function literal in theactions
map that references the loop variablecmd
. There is only one instance of this loop variable, so when after the loop you call the functions stored in theactions
map, all will refer to this single loop variable (which is kept because the functions / closures still have a reference to it), but its value at the time of execution will be the last value set by thefor
loop, which is the last value in thecmds
slice (that is,"update"
, so you'll see"update"
printed 3 times).An easy workaround is to make a copy of this loop variable, so each iteration, each function literal will have its own copy, which is "detached" from the loop variable:
With this, output of
loop1()
(try it on the Go Playground):This it's not an issue of the
for ... range
, it's because the closures refer to the same variable, and you don't use the value of the variable right away, only after the loop. And when you print the value of this variable, all print the same, last value of it.Also see this possible duplicate: Golang: Register multiple routes using range for loop slices/map