El operador range de Go es la forma idiomática de recorrer colecciones. Funciona con slices, arrays, maps, strings y canales, y en cada caso devuelve información diferente. Aprender sus peculiaridades especialmente con strings Unicode evita errores que son fáciles de introducir y difíciles de depurar.
range sobre slices y arrays
package main
import "fmt"
func main() {
frutas := []string{"manzana", "pera", "naranja"}
// índice y valor
for i, fruta := range frutas {
fmt.Printf("[%d] %sn", i, fruta)
}
// solo índice
for i := range frutas {
frutas[i] = strings.ToUpper(frutas[i])
}
// solo valor (ignorar índice con _)
for _, f := range frutas {
fmt.Println(f)
}
}
range sobre maps
Con maps, range devuelve clave y valor. El orden es aleatorio en cada ejecución:
capitales := map[string]string{
"España": "Madrid",
"Francia": "París",
"Alemania": "Berlín",
}
for pais, capital := range capitales {
fmt.Printf("%s ? %sn", pais, capital)
}
// iterar solo las claves
for pais := range capitales {
fmt.Println(pais)
}
range sobre strings: runas, no bytes
Este es el comportamiento que más sorprende. Con una cadena, range itera sobre runas Unicode (puntos de código), no sobre bytes. El índice es la posición del byte donde comienza la runa, no un índice secuencial:
s := "Go: ñoño"
// range sobre string: runas Unicode
for i, r := range s {
fmt.Printf("i=%d runa=%c (%d)n", i, r, r)
}
// i=0 runa=G (71)
// i=1 runa=o (111)
// i=2 runa=: (58)
// i=3 runa= (32)
// i=4 runa=ñ (241) ? ñ ocupa 2 bytes en UTF-8; el siguiente i será 6
// i=6 runa=o (111)
// i=7 runa=ñ (241)
// i=9 runa=o (111)
Si necesitas el índice de bytes y el byte en sí, convierte a []byte antes:
for i, b := range []byte(s) {
fmt.Printf("i=%d byte=%dn", i, b)
}
El error con structs en range
La variable de iteración en range es una copia. Modificarla no afecta al slice original:
type Punto struct{ X, Y int }
puntos := []Punto{{1, 2}, {3, 4}, {5, 6}}
// MAL: p es una copia, el slice no cambia
for _, p := range puntos {
p.X *= 10
}
fmt.Println(puntos) // [{1 2} {3 4} {5 6}] sin cambios
// BIEN: modifica por índice
for i := range puntos {
puntos[i].X *= 10
}
fmt.Println(puntos) // [{10 2} {30 4} {50 6}]
range sobre canales
Con un canal, range lee valores hasta que el canal se cierra. Es la forma idiomática de consumir todos los resultados de un pipeline:
func generador(n int) <-chan int {
ch := make(chan int)
go func() {
for i := 0; i < n; i++ {
ch <- i
}
close(ch) // imprescindible para que range termine
}()
return ch
}
func main() {
for v := range generador(5) {
fmt.Println(v) // 0 1 2 3 4
}
}
Ignorar valores con _
Si no necesitas el índice o la clave, usa _ para ignorarlo. Go exige que todas las variables declaradas se usen; el identificador en blanco es la válvula de escape:
suma := 0
for _, v := range []int{1, 2, 3, 4, 5} {
suma += v
}
fmt.Println(suma) // 15
