iteracion-range-go-slices-mapas

Range en Go: iterar slices, maps y strings con eficiencia

  • 5 min

range es la forma idiomática de recorrer colecciones en Go, ya sean slices, arrays, mapas, strings o canales.

Ya tenemos slices y mapas llenos de datos. Ahora surge la pregunta: ¿cómo recorremos todo eso de forma limpia?

Si vienes de C, podrías sentir la tentación de usar siempre un for i=0; i<len; i++. Funciona, sí, pero en Go tenemos una herramienta mucho más potente y expresiva: la cláusula range.

Vamos a usar range con los principales tipos iterables y a entender qué valor recibimos en cada vuelta.

Sintaxis básica con slices

Cuando aplicamos range sobre un Slice (o un Array), en cada vuelta del bucle nos devuelve dos valores:

  1. El Índice (la posición, empezando en 0).
  2. Una COPIA del Valor en esa posición.
package main

import "fmt"

func main() {
    nombres := []string{"Ana", "Beto", "Carla"}

    for i, nombre := range nombres {
        fmt.Printf("En el índice %d está %s\n", i, nombre)
    }
}
Copied!

Es limpio, legible y seguro (no te saldrás del índice).

El identificador blanco _

Go es muy estricto: si declaras una variable, tienes que usarla. ¿Qué pasa si solo queremos recorrer los valores y nos da igual el índice? Si escribimos for i, valor := ... y no usamos i, el compilador nos dará un error.

Para eso existe el guion bajo _, conocido como identificador blanco. Descarta el valor que no necesitamos.

Solo el valor (ignoramos índice)

for _, nombre := range nombres {
    fmt.Println(nombre)
}
Copied!

Solo el índice (ignoramos valor)

Si solo queremos los índices, podemos omitir la segunda variable. No hace falta poner _ al final.

for i := range nombres {
    // Aquí solo usamos el índice.
    // Útil si queremos modificar el array original (veremos por qué abajo).
    fmt.Println(i)
}
Copied!

El valor de la iteración es una copia

El detalle importante es que la segunda variable que devuelve range es una COPIA del valor original.

Mira este diagrama mental:

Si el slice contiene enteros []int, copiar un número es trivial. Si contiene estructuras grandes, en cambio, la variable de iteración recibe una copia completa del valor (aunque el compilador puede optimizar algunos casos).

Coste de las copias

Iterar por valor en estructuras grandes es lento.

Modificar la copia no cambia el slice

Si modificas la variable del valor (v), NO estás modificando el slice original, solo estás cambiando la copia local.

numeros := []int{10, 20, 30}

for _, v := range numeros {
    v = v * 2 // Modificamos la COPIA 'v'
}

fmt.Println(numeros) // [10 20 30] -> ¡NO HA CAMBIADO!
Copied!

La solución

Si necesitas modificar el slice original o evitar copias pesadas, itera sobre el índice y accede directamente al slice.

for i := range numeros {
    numeros[i] = numeros[i] * 2 // Acceso directo a memoria
}

fmt.Println(numeros) // [20 40 60] -> ¡Ahora sí!
Copied!

Iterar sobre mapas

Usar range con mapas es igual de sencillo, pero en lugar de índice/valor, obtenemos Clave/Valor.

capitales := map[string]string{
    "España":  "Madrid",
    "Francia": "París",
}

for pais, ciudad := range capitales {
    fmt.Printf("La capital de %s es %s\n", pais, ciudad)
}
Copied!

Recuerda: el orden de iteración de un mapa no está garantizado. No asumas que saldrán en el orden en que los insertaste, ni alfabéticamente.

Si solo quieres las claves:

for pais := range capitales {
    fmt.Println("País disponible:", pais)
}
Copied!

range sobre strings

Un string es una secuencia de bytes. Al iterarlo con range, Go decodifica sus puntos de código UTF-8 y devuelve valores rune. Si encuentra una secuencia UTF-8 inválida, devuelve utf8.RuneError.

Fíjate en este ejemplo con la letra “ñ” (que ocupa 2 bytes en UTF-8):

palabra := "Aña"

for i, letra := range palabra {
    fmt.Printf("Índice: %d, Letra: %c, Tipo: %T\n", i, letra, letra)
}
Copied!

Salida:

Índice: 0, Letra: A, Tipo: int32 (rune)
Índice: 1, Letra: ñ, Tipo: int32 (rune)
Índice: 3, Letra: a, Tipo: int32 (rune)   <-- ¡Saltó del 1 al 3!
Copied!
  • El índice i es la posición del byte inicial.
  • El valor letra es un rune (un punto de código Unicode).
  • Fíjate que saltó del índice 1 al 3 porque la ‘ñ’ ocupó los bytes 1 y 2.

Con un for indexado (for i := 0; i < len(s); i++) recorreríamos los bytes de la codificación, no caracteres completos.

Valores devueltos según el tipo

TipoVariables k, v := rangeComportamiento
Slice / Arrayíndice, copia_valorLineal (0 a N). Ojo con las copias grandes.
Mapaclave, copia_valorOrden no garantizado.
Stringíndice_byte, runeDecodifica UTF-8 automáticamente. Saltos de índice variables.
Canal (Channel)valor(Lo veremos en concurrencia) Espera datos hasta que se cierre el canal.