Crear y usar un map
Un tipo map se escribe map[KeyType]ValueType. Créalo con un literal o con make, y luego lee, escribe y borra por clave.
Salida:
31
3
map[bob:26 cy:40]
2 0
Aquí aparecen dos comodidades. Leer una clave que no existe devuelve el valor cero del tipo del valor (counts['z'] es 0), lo que hace que contar con m[k]++ funcione sin preparación previa. Y fmt imprime los maps con las claves ordenadas, lo que viene bien para depurar pero no dice nada del orden de iteración.
make(map[K]V, n) acepta una pista de tamaño opcional. Reserva espacio para unas n entradas; a diferencia de los slices, un map no tiene una capacidad que puedas leer.
Comprobar si existe una clave: comma-ok
Como una clave ausente se lee como el valor cero, m[k] == 0 no distingue entre "ausente" y "guardado como 0". Usa la forma de dos valores:
La forma if v, ok := m[k]; ok { ... } limita v y ok al if. Es una de las líneas más comunes del código Go.
Borrar entradas
delete(m, key) elimina la entrada. Borrar una clave que no está no hace nada, y lo mismo pasa al borrar de un map nil. Para vaciar un map entero, Go 1.21 añadió clear(m), que conserva el map ya reservado para poder reutilizarlo.
Borrar entradas durante un range sobre el mismo map está permitido y es seguro. Una entrada borrada antes de que el bucle llegue a ella no se producirá.
Recorrer un map: el orden es aleatorio
for k, v := range m visita cada entrada una vez, en un orden no especificado. El runtime aleatoriza a propósito el punto de partida, así que dos bucles sobre el mismo map en el mismo programa suelen no coincidir. Ejecuta esto varias veces:
Cualquier código cuya salida dependa del orden de un map es un bug esperando a una ejecución distinta. Los tests que comparan la impresión de una iteración sobre un map son el ejemplo clásico.
Claves ordenadas
Para recorrer un map en orden de clave, obtén las claves, ordénalas e indexa el map. Go 1.23 lo dejó en una línea con los iteradores de los paquetes maps y slices:
En Go 1.22 y anteriores, maps.Keys no existía en la librería estándar. El equivalente es un bucle:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
Otras funciones de maps: maps.Values, maps.Clone (una copia superficial), maps.Equal, maps.Copy(dst, src) y maps.DeleteFunc.
Tipos de clave válidos
Las claves tienen que ser comparables con ==: números, strings, booleanos, punteros, channels, arrays de tipos comparables, structs cuyos campos sean todos comparables y valores de interfaz. Los slices, los maps y las funciones no pueden ser claves.
map[[]int]bool{} // compile error: invalid map key type []int
map[[2]int]bool{} // fine: arrays are comparable
map[struct{ X, Y int }]string{} // fine: a struct key for a grid position
Una clave struct es la forma idiomática de indexar por varios valores a la vez, en lugar de concatenar strings.
Las claves de interfaz compilan aunque el tipo dinámico no sea comparable, y luego provocan un panic en tiempo de ejecución: guardar un []int en un map[any]int falla con runtime error: hash of unhashable type []int.
Las claves de punto flotante funcionan, pero NaN no es igual a sí mismo, así que una clave NaN se puede insertar una y otra vez y nunca volver a leerse. Evita las claves float.
Map de structs
Un map puede contener structs, pero no puedes asignar a un campo de un struct guardado en un map, porque los valores de un map no son direccionables.
Elige valores cuando las entradas son pequeñas y se sustituyen enteras. Elige punteros cuando actualizas campos a menudo o compartes el mismo registro desde varios sitios. Con punteros, una clave ausente devuelve nil, así que ptrs["nope"].Score provoca un panic.
Los maps de slices funcionan igual para los append: groups[k] = append(groups[k], v) no necesita inicialización, porque una clave ausente da un slice nil y append sabe tratar el nil.
Los maps se comportan como referencias
Un valor map apunta a datos compartidos. Asignar un map o pasarlo a una función no copia las entradas: las dos variables ven el mismo map.
Por eso una función puede rellenar un map sin devolverlo, a diferencia de un slice al que hace append.
El panic del map nil
El valor cero de un map es nil. Un map nil se lee como un map vacío, pero escribir en él provoca un panic.
Salida:
0 0
recovered: assignment to entry in nil map
El caso del struct es el que muerde en la práctica. Inicializa los campos map en un constructor (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) o de forma perezosa antes de la primera escritura.
Acceso concurrente
Los maps no son seguros para uso concurrente. Si una goroutine escribe mientras otra lee o escribe, el runtime puede detener el programa con fatal error: concurrent map writes (o concurrent map read and map write). Es un error fatal, no un panic, así que recover no puede capturarlo.
Protege el map con un mutex:
Esto siempre imprime 50 50. Usa sync.RWMutex cuando las lecturas superan con mucho a las escrituras. sync.Map existe para dos casos concretos (claves escritas una vez y leídas muchas, o goroutines que trabajan sobre claves disjuntas); para todo lo demás, un mutex y un map normal es más sencillo y suele ser más rápido. Más sobre esto en mutex.
Referencia rápida
| Operación | Código |
|---|---|
| Crear | m := map[string]int{} o make(map[string]int) |
| Insertar o actualizar | m[k] = v |
| Leer (cero si falta) | v := m[k] |
| Comprobar presencia | v, ok := m[k] |
| Borrar | delete(m, k) |
| Vaciar | clear(m) (Go 1.21) |
| Tamaño | len(m) |
| Claves ordenadas | slices.Sorted(maps.Keys(m)) (Go 1.23) |
| Copiar | maps.Clone(m) |
| Comparar | maps.Equal(a, b) |
Un map con valores struct{} es también el tipo conjunto de Go; consulta sets.
Errores comunes
- Escribir en un map nil. Haz siempre
make, también en los campos map de los structs. - Depender del orden de iteración. Ordena las claves.
- Usar
m[k] != 0para comprobar la presencia. Usa comma-ok. - Modificar el campo de un struct a través de
m[k].Field. Sácalo, cámbialo y vuelve a guardarlo, o guarda punteros. - Compartir un map entre goroutines sin un lock. La caída no se puede recuperar.
Preguntas frecuentes
¿Cómo compruebo si existe una clave en un map de Go?
Usa la forma de dos valores de la búsqueda: v, ok := m[key]. ok es true cuando la clave está presente y false cuando no, y en ese caso v es el valor cero. Leer m[key] a secas no permite distinguir una clave ausente de una clave guardada con el valor cero.
¿Por qué el orden de iteración de un map en Go es aleatorio?
El lenguaje no define un orden, y el runtime empieza a propósito cada range en una posición aleatoria para que los programas no puedan llegar a depender de uno. Para recorrerlo en orden de clave, reúne y ordena las claves: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).
¿Cómo obtengo todas las claves de un map en Go?
Desde Go 1.23, maps.Keys(m) devuelve un iterador; conviértelo en un slice con slices.Collect(maps.Keys(m)), o en un slice ordenado con slices.Sorted(maps.Keys(m)). Antes de 1.23, recorre con for k := range m y añade cada clave a un slice.
¿Por qué escribir en un map provoca un panic con "assignment to entry in nil map"?
La variable del map se declaró pero nunca se creó: var m map[string]int es nil. Leer un map nil devuelve valores cero, pero escribir provoca un panic. Créalo antes con m = make(map[string]int) o con un literal m := map[string]int{}. Un campo map dentro de un struct necesita la misma inicialización.
¿Son seguros los maps de Go para uso concurrente?
No. Las escrituras concurrentes, o una escritura a la vez que lecturas, pueden hacer caer el programa con fatal error: concurrent map writes, que recover no puede capturar. Protege el map con un sync.Mutex o un sync.RWMutex, o usa sync.Map para los casos concretos para los que está pensado.