O que é Map em Go?
Um map em Go é uma estrutura de dados nativa que implementa uma tabela hash (hash table), associando chaves a valores. É o equivalente ao dicionário em Python, HashMap em Java ou objeto/Map em JavaScript. Maps permitem inserção, busca e remoção em tempo médio O(1), tornando-os ideais para cenários que exigem acesso rápido por chave.
Maps são uma das estruturas mais usadas no desenvolvimento Go — desde configurações de aplicação, caches em memória, contadores de frequência, até indexação de dados retornados por bancos. Junto com slices e structs, maps formam o trio fundamental de estruturas de dados em Go.
O tipo de um map é escrito como map[KeyType]ValueType, onde KeyType deve ser um tipo comparável (que suporta ==) e ValueType pode ser qualquer tipo, inclusive outro map ou um slice.
Declarando e inicializando maps
Literal de map
// Map de string para int
idades := map[string]int{
"Alice": 30,
"Bruno": 25,
"Carla": 28,
}
// Map vazio (não nil)
config := map[string]string{}
Usando make()
// Map vazio com capacity hint
usuarios := make(map[int]string)
// Com capacity pré-definida (otimização)
cache := make(map[string][]byte, 1000)
O segundo argumento de make() para maps é um hint de capacidade inicial — não um limite. O map cresce automaticamente conforme necessário, mas pré-alocar evita rehashing em cenários onde você sabe o volume de dados.
Nil map vs map vazio
var nilMap map[string]int // nil — leitura ok, escrita panic!
vazioMap := map[string]int{} // vazio — leitura e escrita ok
// Leitura em nil map retorna zero value
fmt.Println(nilMap["chave"]) // 0 (sem panic)
// Escrita em nil map causa panic!
// nilMap["chave"] = 1 // panic: assignment to entry in nil map
Sempre inicialize um map antes de escrever nele. Essa é uma fonte comum de bugs em Go, especialmente quando maps são campos de structs não inicializados.
Operações CRUD com maps
Create/Update — Inserindo e atualizando
scores := make(map[string]int)
// Inserir
scores["Go"] = 95
scores["Python"] = 88
// Atualizar (mesma sintaxe)
scores["Go"] = 97
Read — Lendo valores
valor := scores["Go"] // 97
// Chave inexistente retorna zero value do tipo
inexistente := scores["Java"] // 0 (não panic)
O idioma comma ok
O comma ok é essencial para distinguir entre um valor zero legítimo e uma chave ausente:
valor, ok := scores["Java"]
if ok {
fmt.Printf("Java: %d\n", valor)
} else {
fmt.Println("Java não encontrado no map")
}
// Padrão idiomático compacto
if v, ok := scores["Go"]; ok {
fmt.Printf("Go score: %d\n", v)
}
Esse padrão é análogo ao type assertion com comma ok usado em interfaces.
Delete — Removendo entradas
delete(scores, "Python") // Remove a chave "Python"
delete(scores, "inexistente") // Não causa panic — é um no-op
A função delete() é segura — chamar com uma chave inexistente ou em um nil map não causa panic.
Iterando sobre maps
capitais := map[string]string{
"Brasil": "Brasília",
"Argentina": "Buenos Aires",
"Chile": "Santiago",
}
for pais, capital := range capitais {
fmt.Printf("%s → %s\n", pais, capital)
}
Ordem de iteração é aleatória
Go randomiza propositalmente a ordem de iteração de maps a cada execução. Isso é uma decisão de design para evitar que desenvolvedores dependam de uma ordem específica. Se precisar de ordem determinística:
import "sort"
// Coletar chaves, ordenar, iterar na ordem
chaves := make([]string, 0, len(capitais))
for k := range capitais {
chaves = append(chaves, k)
}
sort.Strings(chaves)
for _, k := range chaves {
fmt.Printf("%s → %s\n", k, capitais[k])
}
Maps e concorrência
Maps em Go não são seguros para acesso concorrente. Se múltiplas goroutines acessam o mesmo map (e pelo menos uma escreve), você terá uma race condition que pode causar crash com a mensagem fatal: concurrent map writes.
Proteção com Mutex
type CacheSeguro struct {
mu sync.RWMutex
dados map[string]string
}
func (c *CacheSeguro) Get(chave string) (string, bool) {
c.mu.RLock()
defer c.mu.RUnlock()
valor, ok := c.dados[chave]
return valor, ok
}
func (c *CacheSeguro) Set(chave, valor string) {
c.mu.Lock()
defer c.mu.Unlock()
c.dados[chave] = valor
}
O uso de defer para unlock é uma prática recomendada — veja mais em defer no glossário.
sync.Map — Map concorrente da standard library
Para cenários específicos de alta concorrência, Go oferece sync.Map:
var sm sync.Map
// Store
sm.Store("chave", "valor")
// Load
if valor, ok := sm.Load("chave"); ok {
fmt.Println(valor.(string))
}
// LoadOrStore — carrega se existe, senão armazena
actual, loaded := sm.LoadOrStore("nova", "default")
// Delete
sm.Delete("chave")
// Range
sm.Range(func(key, value any) bool {
fmt.Printf("%v: %v\n", key, value)
return true // continue iterating
})
sync.Map é otimizado para dois cenários: (1) chaves escritas uma vez e lidas muitas vezes, e (2) múltiplas goroutines lendo/escrevendo conjuntos disjuntos de chaves. Fora esses casos, sync.RWMutex + map regular geralmente tem melhor performance.
Tipos de chave válidos
Qualquer tipo comparável pode ser chave de map:
// Struct como chave
type Coordenada struct {
Lat, Lng float64
}
pontos := map[Coordenada]string{
{-23.55, -46.63}: "São Paulo",
{-22.90, -43.17}: "Rio de Janeiro",
}
// Array (tamanho fixo) como chave — ok
m1 := map[[2]int]string{}
// Slice como chave — NÃO COMPILA
// m2 := map[[]int]string{} // error: invalid map key type
Tipos que não podem ser chaves: slices, maps e funções — pois não são comparáveis com ==.
Performance: map vs slice
| Operação | Map | Slice |
|---|---|---|
| Busca por valor | O(1) médio | O(n) |
| Inserção | O(1) amortizado | O(1) amortizado (append) |
| Remoção | O(1) | O(n) |
| Iteração ordenada | O(n log n) + sort | O(n) |
| Uso de memória | Maior (overhead de hash) | Menor |
Para coleções pequenas (< 20 elementos), um slice com busca linear pode ser mais rápido que um map devido ao menor overhead de memória e melhor localidade de cache. Para coleções maiores ou quando busca por chave é frequente, maps são superiores.
Para entender como otimizar essas escolhas, consulte o tutorial de profiling em Go.
Padrões comuns com maps
Contadores de frequência
func contarPalavras(texto string) map[string]int {
palavras := strings.Fields(texto)
contagem := make(map[string]int, len(palavras))
for _, p := range palavras {
contagem[strings.ToLower(p)]++
}
return contagem
}
Set (conjunto) usando map
Go não tem tipo nativo de set, mas maps com struct{} como valor resolvem:
type Set map[string]struct{}
func (s Set) Add(item string) { s[item] = struct{}{} }
func (s Set) Contains(item string) bool { _, ok := s[item]; return ok }
func (s Set) Remove(item string) { delete(s, item) }
Cache simples
type Cache struct {
dados map[string]resultado
mu sync.RWMutex
}
func (c *Cache) GetOrCompute(chave string, fn func() resultado) resultado {
c.mu.RLock()
if v, ok := c.dados[chave]; ok {
c.mu.RUnlock()
return v
}
c.mu.RUnlock()
c.mu.Lock()
defer c.mu.Unlock()
// Double-check após adquirir write lock
if v, ok := c.dados[chave]; ok {
return v
}
v := fn()
c.dados[chave] = v
return v
}
Para implementações de cache mais robustas com TTL e eviction, veja o tutorial de Redis com Go.
Boas práticas com maps
- Sempre inicialize maps antes de escrever — nil maps causam panic
- Use comma ok para distinguir zero values de chaves ausentes
- Pré-aloque capacity com
make(map[K]V, n)quando souber o volume - Proteja acesso concorrente com mutex ou channels
- Prefira struct keys a strings concatenadas para chaves compostas
- Não dependa da ordem de iteração — ela é randomizada
Para aprofundar seus conhecimentos sobre Go, explore os tutoriais práticos e o guia de módulos Go.
Perguntas frequentes (FAQ)
Qual a diferença entre nil map e map vazio em Go?
Um nil map (var m map[string]int) permite leitura (retorna zero value) mas causa panic na escrita. Um map vazio (map[string]int{} ou make(map[string]int)) permite leitura e escrita normalmente. Sempre inicialize com make() ou literal antes de inserir dados.
Maps em Go são thread-safe?
Não. Maps nativos em Go não são seguros para acesso concorrente. Se múltiplas goroutines acessam o mesmo map e pelo menos uma escreve, use sync.RWMutex para proteger o acesso ou sync.Map para cenários de alta concorrência com padrões específicos de leitura/escrita.
Como verificar se uma chave existe em um map?
Use o idioma comma ok: valor, ok := meuMap[chave]. A variável ok será true se a chave existe e false caso contrário. Isso é essencial porque o acesso a uma chave inexistente retorna o zero value do tipo, que pode ser um valor legítimo (como 0 para int ou "" para string).
Quando usar sync.Map ao invés de mutex com map regular?
Use sync.Map em dois cenários: quando chaves são escritas uma vez e lidas muitas vezes (cache de configuração) ou quando goroutines operam em conjuntos disjuntos de chaves. Para todos os outros casos — especialmente quando há frequência similar de leitura e escrita — um sync.RWMutex com map regular oferece melhor performance.