dictionary - ¿Por qué estas dos variaciones de bucle me dan un comportamiento diferente?
for-loop go (1)
Veo un comportamiento diferente en mi programa que está vinculado a este ciclo particular en mi programa, pero no estoy seguro de entender por qué se comporta de la manera que es.
//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()
}
}
La salida para
loop1()
es
update
update
update
La salida para
loop2()
es
delete
update
create
Fui a buscar en internet y leí lo siguiente
Cuando se extiende sobre un segmento, se devuelven dos valores para cada iteración. El primero es el índice, y el segundo es una copia del elemento en ese índice.
Dice una copia, entonces, ¿eso significa que devuelve una copia de la cadena pero realmente es un puntero a
cmd
variable?
En cuyo caso, cualquier referencia a
cmd
al final del ciclo hará referencia al último elemento de la matriz, por ejemplo, ¿
update
?
¿Significa esto que los elementos de una matriz siempre deben ser referenciados por su índice cuando se usa el método de
range
, y cuál es el caso de uso para usar el elemento que devuelve ya que siempre está actualizando el puntero?
El problema con
loop1()
es que almacena una función literal en el mapa de
actions
que hace referencia a la
variable de bucle
cmd
.
Solo hay una instancia de esta variable de bucle, por lo que cuando después del bucle llame a las funciones almacenadas en el mapa de
actions
, todas se referirán a esta variable de bucle único (que se mantiene porque las funciones / cierres todavía tienen una referencia), pero su valor
en el momento de la ejecución
será el último valor establecido por el bucle
for
, que es el último valor en el segmento de
cmds
(es decir,
"update"
, por lo que verá
"update"
impreso 3 veces).
Una solución fácil es hacer una copia de esta variable de bucle, por lo que cada iteración, cada literal de función tendrá su propia copia, que está "separada" de la variable de bucle:
func loop1() {
actions := make(map[string]func())
for _, cmd := range cmds {
cmd2 := cmd
actions[cmd] = func() {
fmt.Println(cmd2) // Refer to the detached, copy variable!
}
}
for _, action := range actions {
action()
}
}
Con esto, salida de
loop1()
(pruébalo en
Go Playground
):
update
create
delete
Este no es un problema del
for ... range
, es porque los cierres se refieren a la misma variable, y no usas el valor de la variable de inmediato, solo después del ciclo.
Y cuando imprime el valor de esta variable, todos imprimen el mismo último valor.
También vea este posible duplicado: Golang: Registre múltiples rutas usando el rango para segmentos / mapa de bucle