El ciclo for ... range permite recorrer convenientemente los elementos de un slice, mapa, array o string.
Ejemplo:
s := []int{1, 2, 3} for i, v := range s { fmt.Println(i, v) } m := map[string]int{"a":1, "b":2} for k, v := range m { fmt.Println(k, v) }
Matiz clave:
Las variables i, v, k, etc. se reutilizan en todas las iteraciones. Esto se convierte a menudo en la fuente de errores al pasar estas variables por referencia dentro del ciclo o al iniciar goroutines dentro del range.
¿Qué sucederá si dentro del range de un slice se inicia una goroutine capturando la variable de iteración? ¿Cómo evitar el error?
Respuesta: Surge un error típico: dentro de la goroutine se utilizan las variables de ciclo, que después de que el ciclo se complete tendrán el último valor. Para evitarlo, es necesario crear copias locales:
nums := []int{1, 2, 3} for _, v := range nums { go func(val int) { fmt.Println(val) }(v) }
Historia
En un proyecto se utilizó range para llenar un slice a través de varias goroutines, olvidando hacer copias de las variables de ciclo. Todas las goroutines imprimieron el mismo valor: el último del array, lo que arruinó gravemente la lógica del negocio.
Historia
Al hacer range sobre un map, se guardó la referencia al valor en un nuevo slice de punteros. Como resultado, todos los elementos del nuevo slice referenciaban a la misma variable: la que se utilizaba en el ciclo (copia del valor). El bug se manifestó al actualizar estas variables fuera del ciclo (panic: dirección de memoria inválida o datos inesperados).
Historia
En una herramienta interna, al hacer range sobre un string se lanzaron controladores de substrings pesadas, pero para cada iteración obtuve un desplazamiento en bytes, no en caracteres Unicode. Resultado: procesamiento incorrecto para cadenas Unicode, corte incorrecto de caracteres.