ProgramaciónDesarrollador backend Go Middle/Senior

¿Cómo funcionan los bucles for-range sobre slices y maps en Go y qué peculiaridades de copia de valores surgen al usarlos?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

Historia de la pregunta:

La construcción for-range apareció en Go como una forma de iterar sobre colecciones (slices, arrays, maps, cadenas). Los desarrolladores de Go implementaron una optimización: en cada iteración del bucle se produce una copia del valor, en lugar de usarlo directamente por referencia, lo que puede llevar a errores poco evidentes, especialmente con las variables de bucle.

Problema:

Muchos se equivocan al intentar obtener la dirección de una variable dentro de range (por ejemplo, &v), creyendo que obtienen la dirección del elemento de la colección, pero en realidad obtienen la dirección de una variable local.

Solución:

En el bucle for-range se crean nuevas copias de las variables del iterador (key, value) en cada iteración. Para tipos simples esto no tiene consecuencias, pero para estructuras puede llevar a sorpresas al guardar un puntero al elemento: siempre apuntará a la misma variable, y no a diferentes elementos del slice.

Ejemplo de código:

people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // todos los ptrs apuntarán a la misma p }

Características clave:

  • En cada iteración del bucle se crean nuevas copias de las variables key/value
  • Obtener la dirección de value dentro de for-range no devuelve la dirección del elemento en la colección, sino la de una variable temporal
  • Para maps, la iteración ocurre en un orden arbitrario, no hay garantía de secuencialidad

Preguntas engañosas.

¿Qué sucederá al guardar referencias a la variable value dentro de range?

Todas las referencias apuntarán a la misma memoria, ya que value es una variable temporal.

for _, v := range someSlice { ptrs = append(ptrs, &v) } // ¡Todos los ptrs contienen una referencia a la misma variable!

¿Se puede modificar un elemento de la colección a través de la referencia value en range?

No, modificar value no afecta al elemento original de la colección. Para modificarlo, es necesario acceder por índice.

for _, v := range arr { v.Field = 10 // arr no cambiará } for i := range arr { arr[i].Field = 10 // correcto }

¿Garantiza for-range sobre map un orden secuencial en la iteración?

No, el orden de iteración sobre map en Go no está definido y puede ser diferente en cada ejecución de la aplicación.

Errores comunes y anti-patrón

  • Uso de &value en range con la intención de conservar referencias a diferentes elementos
  • Modificar la variable value en lugar de acceder por índice
  • Esperar un orden secuencial en la iteración de map

Ejemplo de la vida real

Caso negativo

Un desarrollador intenta serializar una lista de referencias a elementos de una estructura a través de range y guarda &value en un slice separado. Se obtiene un slice de direcciones idénticas.

Ventajas:

  • Concisión del bucle

Desventajas:

  • Todas las referencias son iguales, al modificar un elemento se modifican "todos"

Caso positivo

Iteran por índice y guardan un puntero al elemento requerido del array:

for i := range arr { ptrs = append(ptrs, &arr[i]) }

Ventajas:

  • Cada puntero apunta a un elemento separado del slice original

Desventajas:

  • La sintaxis es más larga, pero el resultado es predecible