Iteración sobre map en Go: cómo no pisar el rastrillo (y no perder datos)
\n\nSi alguna vez has programado en Go, seguramente te has encontrado con map, una de las estructuras de datos más potentes y engañosas. Parece sencillo: usas range, recorres las claves, obtienes los valores. Pero el diablo, como siempre, está en los detalles. La pregunta "Iterating over all the keys of a map" en Stack Overflow ha acumulado miles de visitas, y no es casualidad.
Hoy analizaremos los principales errores al iterar sobre map en Go que cometen incluso desarrolladores experimentados. Descubrirás por qué el orden de recorrido es una lotería, cómo trabajar de forma segura con map en gorutinas y qué hacer si el map resulta ser nil. ¡Allá vamos!
Error n.º 1: Esperar un orden (o "¿Dónde están mis claves?")
\n\nLa trampa más común es creer que range devolverá las claves en el mismo orden en que las agregaste. En Go no es así. El orden de iteración sobre un map es fundamentalmente no determinista.
Observa este código:
\n\npackage main\n\nimport "fmt"\n\nfunc main() {\n m := map[string]int{\n "apple": 1,\n "banana": 2,\n "cherry": 3,\n }\n\n for i := 0; i < 3; i++ {\n fmt.Println("Iteración", i+1)\n for k, v := range m {\n fmt.Printf(" %s: %d\\n", k, v)\n }\n }\n}\n\n\nLo más probable es que cada ejecución muestre un orden diferente. Esto no es un error, sino una característica: Go aleatoriza el orden a propósito para que los desarrolladores no se acostumbren a él ni construyan lógica basada en una secuencia aleatoria.
\n\n¿Cómo evitarlo?
\n- \n
- No confíes en el orden: esta es la regla principal. \n
- Si el orden es importante, usa un
[]stringpara almacenar las claves por separado y ordénalas: \n
package main\n\nimport (\n "fmt"\n "sort"\n)\n\nfunc main() {\n m := map[string]int{"apple": 1, "banana": 2, "cherry": 3}\n\n keys := make([]string, 0, len(m))\n for k := range m {\n keys = append(keys, k)\n }\n sort.Strings(keys)\n\n for _, k := range keys {\n fmt.Printf("%s: %d\\n", k, m[k])\n }\n}\n\n\nConsejo: Si necesitas mantener el orden de inserción, considera la librería github.com/elliotchance/orderedmap o crea tu propia envoltura basada en dos estructuras (map + slice).
Error n.º 2: Iteración con escritura simultánea (data race)
\n\nEn Go, map no es seguro para acceso concurrente. Si una gorutina lee (itera) un map mientras otra escribe en él al mismo tiempo, obtendrás un fatal error: concurrent map iteration and map write.
\n\nEjemplo de error fatal:
\n\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n m := make(map[int]int)\n\n go func() {\n for i := 0; i < 100; i++ {\n m[i] = i\n }\n }()\n\n go func() {\n for k, v := range m {\n fmt.Println(k, v)\n }\n }()\n\n time.Sleep(time.Second)\n}\n\n\nEste código fallará con un pánico. Y no siempre: la condición de carrera puede manifestarse en la ejecución número 1000.
\n\n¿Cómo evitarlo?
\n\nHay tres formas probadas:
\n\n- \n
- sync.Mutex — el mutex clásico para sincronizar el acceso: \n
package main\n\nimport (\n "fmt"\n "sync"\n)\n\ntype SafeMap struct {\n mu sync.Mutex\n m map[int]int\n}\n\nfunc (s *SafeMap) Set(key, value int) {\n s.mu.Lock()\n defer s.mu.Unlock()\n s.m[key] = value\n}\n\nfunc (s *SafeMap) Iterate(f func(key, value int) bool) {\n s.mu.Lock()\n defer s.mu.Unlock()\n for k, v := range s.m {\n if !f(k, v) {\n break\n }\n }\n}\n\nfunc main() {\n sm := SafeMap{m: make(map[int]int)}\n sm.Set(1, 10)\n sm.Set(2, 20)\n\n sm.Iterate(func(k, v int) b