Menu

range en Golang: todas sus formas, qué produce y sus trampas

Qué produce for range con slices, arrays, strings, maps, channels, enteros y funciones iteradoras, por qué la variable del valor es una copia, qué pasa si modificas un slice o un map mientras lo recorres y range sobre funciones en Go 1.23.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

range es la forma de for que recorre una colección. Te da uno o dos valores por iteración, y cuáles son depende del tipo que recorres:

OperandoPrimer valorSegundo valorNotas
slice []T, array [n]Tíndice intelemento T, una copia
puntero a array *[n]Tíndiceelemento
stringíndice en bytes intrunedecodifica UTF-8
map map[K]Vclave Kvalor Vorden aleatorio
channel chan Telemento Tningunohasta que se cierra el channel
entero n0 a n menos 1ningunoGo 1.22
func(yield func() bool)ningunoningunoGo 1.23
func(yield func(V) bool)VningunoGo 1.23
func(yield func(K, V) bool)KVGo 1.23

Elegir qué variables recibes

for i, v := range s {}  // both
for i := range s {}     // index (or key) only
for _, v := range s {}  // value only
for range s {}          // neither: just repeat len(s) times

Declarar una variable que no usas es un error de compilación, así que quita o deja en blanco las que no necesites. for range 3 {} es la forma más corta de repetir algo tres veces.

El valor es una copia

En cada iteración, el elemento se copia en la variable del valor. Cambiar la variable no cambia la colección:

Copiar también cuesta tiempo cuando los elementos son structs grandes. Para un slice de structs grandes, for i := range s con s[i] evita la copia.

Los strings producen runes

Recorrer un string decodifica UTF-8. El primer valor es el desplazamiento en bytes donde empieza cada carácter, así que puede saltarse números:

Para recorrer bytes en lugar de caracteres, usa un bucle con índice, for i := 0; i < len(s); i++, o recorre []byte(s). Consulta runes y bytes para los detalles de UTF-8.

Maps: orden aleatorio

El orden de iteración de un map no está especificado, y el runtime lo aleatoriza para que el código no pueda llegar a depender de un orden. Para una salida determinista, ordena las claves:

Borrar entradas mientras recorres es seguro, y una entrada borrada a la que aún no se ha llegado no se produce. Añadir entradas está permitido pero es impredecible: una clave nueva puede aparecer o no más adelante en el mismo bucle. fmt.Println de un map imprime las claves ordenadas, y por eso la última línea es estable.

Channels: hasta que se cierran

Recorrer un channel recibe valores hasta que el channel se cierra y se vacía. Si nadie lo cierra, el bucle se bloquea para siempre (y si todas las goroutines están bloqueadas, el runtime se detiene con fatal error: all goroutines are asleep - deadlock!):

El que cierra el channel es el emisor, nunca el receptor. Consulta channels para los channels con buffer y select.

Enteros (Go 1.22)

range n cuenta desde 0 hasta n, sin incluirlo. El tipo de la variable del bucle es el tipo de n:

for i := range 3 {}         // i is int: 0, 1, 2
for i := range uint8(3) {}  // i is uint8
for range 0 {}              // runs zero times

Un n negativo también se ejecuta cero veces. n se evalúa una vez, antes de que empiece el bucle.

Qué evalúa range, y cuándo

La expresión que va detrás de range se evalúa una vez, antes de la primera iteración. Con un slice, range usa entonces esa cabecera de slice, así que su longitud queda fijada:

El 99 añadido nunca se visita porque la longitud del bucle se fijó en 3. Pero nums[2] = 30 sí se ve, porque en ese momento nums todavía comparte su array subyacente con el slice que se recorre. Intercambia las dos líneas y el 30 también desaparece: el append supera la capacidad de 3, reserva un array nuevo y la escritura cae allí. Recorrer un valor array copia primero el array entero, así que el cambio en arr[2] no se ve; recorre &arr o arr[:] para evitar la copia.

range sobre funciones (Go 1.23)

Desde Go 1.23, range también acepta funciones iteradoras. Un iterador recibe un callback yield, lo llama una vez por valor y se detiene cuando yield devuelve false (lo que ocurre cuando el cuerpo del bucle ejecuta break o return):

iter.Seq[V] e iter.Seq2[K, V] son los nombres estándar de las dos formas de iterador. La librería estándar los devuelve desde slices.All, slices.Values, slices.Backward, maps.Keys, maps.Values, maps.All, y en Go 1.24 desde strings.SplitSeq, strings.Lines y bytes.SplitSeq. El iterador tiene que respetar el valor de retorno de yield: llamar otra vez a yield después de que devolviera false provoca un panic.

Las variables de bucle son por iteración (Go 1.22)

Desde Go 1.22, las variables que declara for ... range son nuevas en cada iteración. Capturar v en una closure o una goroutine captura el valor de esa iteración, no una variable compartida que acaba conteniendo el último elemento. Antes de Go 1.22 necesitabas v := v dentro del bucle; esa línea ahora sobra. La regla la fija la versión go de go.mod, así que un módulo antiguo mantiene el comportamiento antiguo.

Errores comunes

  • Tomar la dirección de la variable del valor. Antes de Go 1.22, ptrs = append(ptrs, &v) guardaba siempre la misma dirección. Desde 1.22 guarda direcciones distintas, pero cada una apunta a una copia, no al elemento del slice. Usa &items[i] para apuntar dentro del slice.
  • Esperar un orden en los maps. Una salida que parece ordenada en un test pequeño no se mantendrá ordenada.
  • Recorrer un channel que nadie cierra. El bucle, y la goroutine que lo ejecuta, esperan para siempre.
  • Recorrer un array grande por valor. Primero se copia el array entero. Recorre un slice de él.

Preguntas frecuentes

¿Qué devuelve range en Go?

Depende del operando. Para un slice o un array: índice y elemento. Para un string: índice en bytes y rune. Para un map: clave y valor. Para un channel: cada valor recibido. Para un entero n (Go 1.22): de 0 a n menos 1. Para una función iteradora (Go 1.23): lo que produzca la función. Puedes omitir la segunda variable o descartar la primera con _.

¿Por qué modificar el valor de range no cambia mi slice?

La variable del valor es una copia del elemento. for _, v := range items { v.Price = 0 } solo cambia la copia. Usa el índice: for i := range items { items[i].Price = 0 }, o recorre un slice de punteros.

¿Se puede usar range sobre un entero en Go?

Sí, desde Go 1.22: for i := range 5 { ... } se ejecuta con i de 0 a 4. for range 5 { ... } repite cinco veces sin variable. El go.mod del módulo tiene que indicar go 1.22 o posterior.

¿Es seguro borrar de un map mientras lo recorres?

Sí. Borrar una entrada a la que aún no se ha llegado hace que no se produzca, y borrar la entrada actual no da problemas. Añadir entradas durante el bucle también está permitido, pero una entrada nueva puede visitarse o no, así que no dependas de ello.

Coddy programming languages illustration

Aprende a programar con Coddy

COMENZAR