Un mapa en Go es una colección clave-valor para buscar datos por un identificador, como un diccionario donde cada clave apunta a un valor concreto.
Hasta ahora hemos visto cómo guardar datos en fila india con arrays y slices. Funcionan bien si sabemos el orden o el índice numérico. Para buscar por un DNI, un nombre o cualquier otra clave comparable usamos mapas.
En otros lenguajes se llaman diccionarios o hash maps. El concepto es el mismo: una estructura sin orden de iteración garantizado que asocia una clave única con un valor.
Qué es un mapa en Go
Un mapa en Go es una referencia a una tabla hash.
La sintaxis para definirlo es: map[TipoClave]TipoValor.
- Tipo de la clave: puede ser cualquier tipo comparable, como booleanos, números, strings, canales, punteros y determinados arrays, estructuras e interfaces. No puede ser un slice, un mapa ni una función.
- Tipo del valor: puede ser cualquier tipo, incluso otro mapa.
Declaración e inicialización
Aquí hay una trampa en la que caen casi todos los principiantes.
Si declaras un mapa solo con var, obtienes un mapa nil. Un mapa nil se comporta de forma curiosa:
- Puedes leer de él (te devuelve ceros).
- No puedes asignar una entrada: provoca un
panic.
package main
import "fmt"
func main() {
// ❌ FORMA PELIGROSA
var colores map[string]string // Es nil
// colores["rojo"] = "#FF0000" // ERROR: panic: assignment to entry in nil map
// ✅ FORMA CORRECTA 1: make()
// Inicializa el mapa y reserva memoria
puntuaciones := make(map[string]int)
puntuaciones["Luis"] = 10
// ✅ FORMA CORRECTA 2: Literal
// Muy útil para valores iniciales
capitales := map[string]string{
"España": "Madrid",
"Francia": "París",
"Italia": "Roma", // ¡Ojo a la coma final obligatoria!
}
fmt.Println(capitales)
}Usa make(map[...]) o un literal si vas a escribir en el mapa. Un mapa nil sigue siendo útil como valor vacío de solo lectura.
Operaciones básicas
Trabajar con mapas es muy intuitivo:
Insertar o actualizar
No hay diferencia. Si la clave no existe, la crea. Si existe, la machaca.
m := make(map[string]int)
m["edad"] = 30
m["edad"] = 31 // ActualizaLeer un valor
valor := m["edad"]Borrar una clave
Go tiene una función nativa (built-in) llamada delete.
delete(m, "edad")Curiosidad: Si intentas borrar una clave que no existe, no pasa nada (no da error).
El valor cero y el patrón comma, ok
¿Qué pasa si intentas leer una clave que no existe?
En otros lenguajes te daría una excepción o null. En Go te devuelve el Zero Value del tipo de valor.
puntuaciones := map[string]int{"Ana": 10}
nota := puntuaciones["Pedro"] // Pedro no existe
fmt.Println(nota) // Imprime 0 (porque es el zero value de int)Esto es un problema. ¿Pedro tiene un 0 porque suspendió, o tiene un 0 porque no se presentó al examen (no está en el mapa)?
Para distinguir estos casos, usamos la asignación múltiple, conocida como el patrón “Comma Ok”:
valor, ok := mapa[clave]valor: El valor almacenado (o cero si no está).ok: Un booleano (truesi la clave existe,falsesi no).
if nota, ok := puntuaciones["Pedro"]; ok {
fmt.Println("La nota de Pedro es:", nota)
} else {
fmt.Println("Pedro no está en la lista")
}Este patrón es habitual en Go. Úsalo cuando necesites distinguir entre una clave ausente y un valor cero almacenado.
Iterar sobre mapas
Para recorrer un mapa usamos el bucle for con range, igual que en los slices, pero obtenemos clave y valor.
menu := map[string]float64{
"Café": 1.50,
"Tostada": 2.00,
"Zumo": 3.00,
}
for plato, precio := range menu {
fmt.Printf("%s cuesta %.2f€\n", plato, precio)
}El orden no está garantizado
Esto es crítico. Los mapas en Go no están ordenados.
Cada vez que ejecutes el for range anterior, es posible que los elementos salgan en un orden distinto. Simplemente no dependas de ese orden.
Si necesitas iterar en orden (por ejemplo, alfabético), debes:
- Extraer las claves a un slice.
- Ordenar ese slice.
- Iterar el slice y buscar en el mapa.
Mapas y datos compartidos
Al igual que ocurre con los slices, al pasar un mapa a una función se copia un descriptor pequeño, pero ambos apuntan a la misma tabla interna. Si la función lo modifica, el mapa original cambia.
func modificarMapa(m map[string]int) {
m["nuevo"] = 999
}
func main() {
miMapa := make(map[string]int)
modificarMapa(miMapa)
fmt.Println(miMapa) // Imprime map[nuevo:999]
}