La compatibilidad de iteradores con el ecosistema de Go se define por la integración profunda de los tipos funcionales iter.Seq e iter.Seq2 dentro de los paquetes nucleares de la biblioteca estándar, permitiendo que las estructuras de datos nativas operen como fuentes de secuencias estandarizadas. Esta convergencia técnica asegura que los desarrolladores puedan transicionar de bucles imperativos tradicionales a flujos de datos composibles sin abandonar las garantías de rendimiento del lenguaje.
Este comportamiento existe para mitigar la fragmentación en la manipulación de colecciones. Antes de Go 1.23, realizar operaciones comunes como extraer las llaves de un mapa o filtrar un slice requería la creación de colecciones intermedias o la escritura de bucles repetitivos. La adopción de iteradores en la stdlib resuelve este problema al proporcionar una interfaz común que evita alocaciones innecesarias en el heap, permitiendo que funciones de diferentes paquetes se comuniquen mediante contratos de funciones iteradoras en lugar de tipos de datos concretos.
Integración en paquetes slices y maps
El mecanismo de interoperabilidad se apoya en funciones constructoras de iteradores que respetan la assignability de los nuevos tipos genéricos. En el paquete slices, funciones como slices.All y slices.Values transforman un slice en secuencias iter.Seq2[int, E] e iter.Seq[E] respectivamente. Lo fundamental es que estas funciones no ejecutan la iteración de inmediato; devuelven un closure que captura la referencia al slice original, postergando el recorrido hasta que un bucle for-range invoca activamente la función yield.
Para el paquete maps, la integración sigue un patrón similar con maps.All, maps.Keys y maps.Values. Estas funciones permiten desacoplar la lógica de acceso a los datos de la lógica de procesamiento. Al utilizar estos iteradores, el programador puede migrar código existente que dependía de la creación manual de slices temporales hacia un modelo de “consumo bajo demanda”. Por ejemplo, la función slices.Collect actúa como un puente de salida, permitiendo materializar cualquier iter.Seq de vuelta en un slice de forma eficiente, pre-calculando el tamaño si la fuente lo permite para optimizar la capacidad inicial.
package main
import (
"fmt"
"maps"
"slices"
)
func main() {
// Datos originales
scores := map[string]int{"alpha": 10, "beta": 25, "gamma": 15}
// maps.Keys devuelve un iter.Seq[string]
// No se crea un slice intermedio aquí
iterator := maps.Keys(scores)
// slices.Collect consume el iterador y genera un slice concreto
keys := slices.Collect(iterator)
// Ordenamos el slice resultante para un output predecible
slices.Sort(keys)
fmt.Println("Keys:", keys) // Output: Keys: [alpha beta gamma]
// Uso de slices.All para obtener índice y valor de forma estandarizada
languages := []string{"Go", "Python", "Kotlin"}
for i, lang := range slices.All(languages) {
if i%2 == 0 {
fmt.Printf("Even index %d: %s\n", i, lang)
}
}
// Output:
// Even index 0: Go
// Even index 2: Kotlin
}
GoLa combinación de maps.Keys con slices.Collect sustituye de manera idiomática el patrón anterior de iterar manualmente con range sobre un mapa para llenar un slice previamente alocado con make.
La mutabilidad de la fuente original durante la iteración diferida
Un comportamiento crítico del diseño de estos iteradores es que mantienen una referencia al underlying array o la estructura de mapa original, no una copia congelada de los datos. Si el programador obtiene un iterador mediante slices.All(s) y, antes de consumirlo en un bucle, modifica el contenido del slice s, el iterador reflejará los valores actualizados. Esto sucede porque el iterador es técnicamente una función que accede a la estructura de datos en el momento exacto en que yield es ejecutado.
Este comportamiento puede ser un edge case peligroso en entornos concurrentes. Si un mapa es modificado (se añaden o eliminan llaves) mientras un iterador obtenido de maps.All está siendo recorrido en otra goroutine, se disparará un pánico de “concurrent map read and map write”, exactamente igual que si se estuviera usando un bucle for-range directo sobre el mapa. La migración a iteradores no añade una capa de seguridad contra concurrencia; los iteradores de la stdlib son tan “vivos” como las colecciones que los originan, lo que requiere mantener las protecciones habituales mediante mutexes o canales si la estructura de datos es compartida.
- Módulo: Iteradores
- Artículo número: #67