Se slices são a espinha dorsal das sequências em Go, maps são a espinha dorsal das associações. Um map armazena pares de chave e valor, permitindo recuperar qualquer valor em tempo constante — O(1) — a partir de sua chave. Essa característica os torna indispensáveis para contagem, agrupamento, indexação, cache e configuração.
Go implementa maps como tabelas hash. Você não precisa entender os detalhes internos para usá-los bem, mas saber que são tabelas hash explica dois comportamentos que todo programador Go precisa conhecer: a ordem de iteração é aleatória, e maps não são seguros para uso concorrente sem sincronização.
Criando maps
Com literal de map:
package main
import "fmt"
func main() {
capitais := map[string]string{
"Brasil": "Brasília",
"Argentina": "Buenos Aires",
"Chile": "Santiago",
"Peru": "Lima",
}
fmt.Println(capitais["Brasil"]) // Brasília
fmt.Println(capitais["Argentina"]) // Buenos Aires
}
A sintaxe map[TipoChave]TipoValor define o tipo do map. Chaves e valores podem ser de qualquer tipo, com uma restrição importante: o tipo da chave precisa ser comparável — ou seja, deve suportar os operadores == e !=. Tipos como int, string, bool, structs com campos comparáveis e ponteiros são válidos como chaves. Slices, maps e funções não são comparáveis e não podem ser usados como chaves.
Com make:
estoque := make(map[string]int)
estoque["teclado"] = 15
estoque["mouse"] = 32
estoque["monitor"] = 7
fmt.Println(estoque) // map[monitor:7 mouse:32 teclado:15]
make cria um map vazio e pronto para uso. A diferença entre make(map[string]int) e declarar var m map[string]int é crítica: um map declarado com var mas não inicializado tem valor nil, e tentar escrever nele causa pânico em tempo de execução:
var m map[string]int
m["chave"] = 1 // PANIC: assignment to entry in nil map
Sempre inicialize maps com make ou com um literal antes de escrever neles.
Lendo valores e verificando existência
Ler uma chave inexistente em um map não causa erro — Go retorna o valor zero do tipo do valor:
estoque := map[string]int{
"teclado": 15,
}
quantidade := estoque["mouse"]
fmt.Println(quantidade) // 0 — chave não existe, retorna zero
Isso pode ser ambíguo: o valor 0 pode significar "chave não existe" ou "o valor associado à chave é realmente zero". Para distinguir os dois casos, Go oferece a forma de dois retornos — o idioma mais importante ao trabalhar com maps:
quantidade, existe := estoque["mouse"]
if existe {
fmt.Println("Estoque de mouse:", quantidade)
} else {
fmt.Println("Produto não encontrado no estoque")
}
A variável existe é um bool que indica se a chave estava presente no map, independentemente do valor. Esse padrão é chamado de comma ok e aparece em outros contextos do Go, como leitura de channels e asserções de tipo.
Combinando com a inicialização no if:
if qtd, ok := estoque["teclado"]; ok {
fmt.Println("Teclado em estoque:", qtd)
}
Removendo entradas
A função embutida delete remove uma chave de um map. Se a chave não existir, a operação é silenciosamente ignorada — não há erro:
estoque := map[string]int{
"teclado": 15,
"mouse": 32,
}
delete(estoque, "mouse")
delete(estoque, "monitor") // chave inexistente — sem efeito
fmt.Println(estoque) // map[teclado:15]
Iterando sobre maps
A iteração usa for range. Como mencionado, a ordem é aleatória e não garantida em nenhuma versão do Go:
populacao := map[string]int{
"São Paulo": 12_300_000,
"Rio de Janeiro": 6_700_000,
"Brasília": 3_000_000,
"Salvador": 2_900_000,
}
for cidade, habitantes := range populacao {
fmt.Printf("%-20s %d habitantes\n", cidade, habitantes)
}
Quando apenas as chaves são necessárias:
for cidade := range populacao {
fmt.Println(cidade)
}
Iteração em ordem definida. Quando a ordem importa, extraia as chaves para um slice, ordene-o e itere pelo slice:
import "sort"
chaves := make([]string, 0, len(populacao))
for k := range populacao {
chaves = append(chaves, k)
}
sort.Strings(chaves)
for _, k := range chaves {
fmt.Printf("%-20s %d\n", k, populacao[k])
}
Padrões comuns com maps
Contagem de frequência. Um dos usos mais clássicos de maps é contar ocorrências:
package main
import (
"fmt"
"strings"
)
func main() {
texto := "go é simples go é eficiente go é concorrente"
palavras := strings.Fields(texto)
frequencia := make(map[string]int)
for _, p := range palavras {
frequencia[p]++
}
for palavra, count := range frequencia {
fmt.Printf("%-15s %d\n", palavra, count)
}
}
O padrão frequencia[p]++ funciona porque o valor zero de int é 0 — ao incrementar uma chave inexistente, Go a inicializa com 0 e então incrementa para 1.
Agrupamento. Maps cujo valor é um slice permitem agrupar elementos:
package main
import "fmt"
func main() {
pessoas := []struct {
Nome string
Departamento string
}{
{"Ana", "Engenharia"},
{"Bruno", "Marketing"},
{"Carla", "Engenharia"},
{"Diego", "Marketing"},
{"Elena", "Engenharia"},
}
porDepartamento := make(map[string][]string)
for _, p := range pessoas {
porDepartamento[p.Departamento] = append(
porDepartamento[p.Departamento], p.Nome,
)
}
for dept, nomes := range porDepartamento {
fmt.Printf("%s: %v\n", dept, nomes)
}
}
Set simulado. Go não possui um tipo Set nativo. O padrão idiomático usa map[T]struct{}, onde struct{} ocupa zero bytes de memória:
visitados := make(map[string]struct{})
urls := []string{"go.dev", "github.com", "go.dev", "pkg.go.dev"}
for _, url := range urls {
if _, visto := visitados[url]; visto {
fmt.Println("Duplicado ignorado:", url)
continue
}
visitados[url] = struct{}{}
fmt.Println("Processando:", url)
}
Maps de maps
Para estruturas hierárquicas, maps podem ter outros maps como valor:
config := map[string]map[string]string{
"banco": {
"host": "localhost",
"porta": "5432",
"nome": "appdb",
},
"cache": {
"host": "localhost",
"porta": "6379",
},
}
fmt.Println(config["banco"]["host"]) // localhost
fmt.Println(config["cache"]["porta"]) // 6379
Ao trabalhar com maps aninhados, verifique a existência do map externo antes de acessar o interno para evitar pânico:
if db, ok := config["banco"]; ok {
fmt.Println(db["nome"])
}
Tamanho e maps vazios
A função len retorna o número de pares chave-valor:
m := map[string]int{"a": 1, "b": 2, "c": 3}
fmt.Println(len(m)) // 3
Para verificar se um map está vazio:
if len(m) == 0 {
fmt.Println("map vazio")
}
Concorrência e maps
Maps em Go não são seguros para uso concorrente. Se múltiplas goroutines acessam o mesmo map simultaneamente e ao menos uma delas está escrevendo, o comportamento é indefinido e pode causar corrupção de dados ou pânico.
Para uso concorrente, existem duas abordagens principais. A primeira é proteger o map com um sync.RWMutex:
import "sync"
type MapaConcorrente struct {
mu sync.RWMutex
dados map[string]int
}
func (m *MapaConcorrente) Set(chave string, valor int) {
m.mu.Lock()
defer m.mu.Unlock()
m.dados[chave] = valor
}
func (m *MapaConcorrente) Get(chave string) (int, bool) {
m.mu.RLock()
defer m.mu.RUnlock()
v, ok := m.dados[chave]
return v, ok
}
A segunda é usar o sync.Map da biblioteca padrão, otimizado para casos onde chaves são escritas uma vez e lidas muitas vezes:
import "sync"
var m sync.Map
m.Store("chave", 42)
valor, ok := m.Load("chave")
if ok {
fmt.Println(valor) // 42
}
m.Delete("chave")
O tema de concorrência será aprofundado futuramente. Por ora, a regra simples é: se mais de uma goroutine usa o mesmo map, proteja o acesso.
Resumo do que foi coberto
Este artigo cobriu maps em Go de forma completa: criação com literais e make, a distinção crucial entre map nil e map vazio, leitura com o idioma comma ok, remoção com delete, iteração ordenada, padrões de contagem, agrupamento e simulação de set, maps aninhados e as considerações de segurança para uso concorrente.
Referências e leituras complementares
-
Go Blog: Go maps in action — Artigo oficial sobre maps, com casos de uso práticos. https://go.dev/blog/maps
-
A Tour of Go — Maps — Introdução interativa ao tipo map. https://go.dev/tour/moretypes/19
-
Go by Example: Maps — Exemplos práticos comentados. https://gobyexample.com/maps
-
Documentação do pacote sync — sync.Map — Referência do map concorrente da biblioteca padrão. https://pkg.go.dev/sync#Map
-
Especificação da linguagem Go — Map types — Definição formal do tipo map. https://go.dev/ref/spec#Map_types
-
Go by Example: Sorting — Como ordenar slices de strings e ints, útil para iteração ordenada em maps. https://gobyexample.com/sorting