Criando e usando um map
Um tipo map se escreve map[TipoDaChave]TipoDoValor. Crie um com um literal ou com make, e depois leia, escreva e apague pela chave.
Saída:
31
3
map[bob:26 cy:40]
2 0
Duas conveniências aparecem aqui. Ler uma chave ausente devolve o valor zero do tipo do valor (counts['z'] é 0), o que faz a contagem com m[k]++ funcionar sem preparação nenhuma. E o fmt imprime maps com as chaves ordenadas, o que ajuda na depuração, mas não diz nada sobre a ordem de iteração.
make(map[K]V, n) aceita uma sugestão de tamanho opcional. Ela pré-aloca espaço para cerca de n entradas; diferente dos slices, um map não tem uma capacidade que você possa consultar.
Verificando se uma chave existe: vírgula-ok
Como uma chave ausente é lida como valor zero, m[k] == 0 não diferencia "ausente" de "guardado como 0". Use a forma com dois valores:
O formato if v, ok := m[k]; ok { ... } mantém v e ok restritos ao if. É uma das linhas mais comuns em código Go.
Apagando entradas
delete(m, key) remove a entrada. Apagar uma chave que não existe não faz nada, e apagar de um map nil também não. Para esvaziar um map inteiro, o Go 1.21 trouxe clear(m), que mantém o map alocado para ser reaproveitado.
Apagar entradas durante um range sobre o mesmo map é permitido e seguro. Uma entrada apagada antes de o laço chegar nela não é entregue.
Percorrendo: a ordem é aleatória
for k, v := range m visita cada entrada uma vez, em uma ordem não especificada. O runtime embaralha o ponto de partida de propósito, então dois laços sobre o mesmo map no mesmo programa muitas vezes discordam. Execute isto algumas vezes:
Qualquer código cuja saída dependa da ordem do map é um bug esperando por outra execução. Testes que comparam a saída impressa de uma iteração de map são o exemplo clássico.
Chaves ordenadas
Para visitar um map na ordem das chaves, pegue as chaves, ordene e acesse o map por elas. O Go 1.23 transformou isso em uma linha, com iteradores dos pacotes maps e slices:
No Go 1.22 e anteriores, maps.Keys não existia na biblioteca padrão. O equivalente é um laço:
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
Outras funções de maps: maps.Values, maps.Clone (uma cópia rasa), maps.Equal, maps.Copy(dst, src) e maps.DeleteFunc.
Tipos de chave válidos
As chaves precisam ser comparáveis com ==: números, strings, booleanos, ponteiros, channels, arrays de tipos comparáveis, structs cujos campos são todos comparáveis e valores de interface. Slices, maps e funções não podem ser chaves.
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
Uma chave struct é o jeito idiomático de usar vários valores como chave ao mesmo tempo, em vez de concatenar strings.
Chaves de interface compilam mesmo quando o tipo dinâmico não é comparável, e aí causam panic em tempo de execução: guardar um []int em um map[any]int falha com runtime error: hash of unhashable type []int.
Chaves de ponto flutuante funcionam, mas NaN não é igual a si mesmo, então uma chave NaN pode ser inserida várias vezes e nunca lida de volta. Evite chaves float.
Map de structs
Um map pode guardar structs, mas você não pode atribuir a um campo de uma struct guardada em um map, porque valores de map não são endereçáveis.
Escolha valores quando as entradas são pequenas e substituídas por inteiro. Escolha ponteiros quando você atualiza campos com frequência ou compartilha o mesmo registro a partir de vários lugares. Com ponteiros, uma chave ausente devolve nil, então ptrs["nope"].Score causa panic.
Maps de slices funcionam do mesmo jeito para appends: groups[k] = append(groups[k], v) não precisa de inicialização, porque uma chave ausente dá um slice nil e o append lida com nil.
Maps se comportam como referência
Um valor de map aponta para dados compartilhados. Atribuir um map ou passá-lo para uma função não copia as entradas: as duas variáveis enxergam o mesmo map.
É por isso que uma função pode preencher um map sem devolvê-lo, diferente de um slice em que ela faz append.
O panic do map nil
O valor zero de um map é nil. Um map nil é lido como um map vazio, mas escrever nele causa panic.
Saída:
0 0
recovered: assignment to entry in nil map
O caso da struct é o que pega na prática. Inicialize campos map em um construtor (func NewCache() *Cache { return &Cache{data: map[string]string{}} }) ou de forma preguiçosa antes da primeira escrita.
Acesso concorrente
Maps não são seguros para uso concorrente. Se uma goroutine escreve enquanto outra lê ou escreve, o runtime pode parar o programa com fatal error: concurrent map writes (ou concurrent map read and map write). É um erro fatal, não um panic, então o recover não consegue capturá-lo.
Proteja o map com um mutex:
Isto sempre imprime 50 50. Use sync.RWMutex quando as leituras superam muito as escritas. sync.Map existe para dois casos específicos (chaves escritas uma vez e lidas muitas vezes, ou goroutines trabalhando em chaves disjuntas); para todo o resto, um mutex com um map comum é mais simples e normalmente mais rápido. Mais sobre isso em mutex.
Referência rápida
| Operação | Código |
|---|---|
| Criar | m := map[string]int{} ou make(map[string]int) |
| Inserir ou atualizar | m[k] = v |
| Ler (zero se ausente) | v := m[k] |
| Verificar presença | v, ok := m[k] |
| Apagar | delete(m, k) |
| Remover tudo | clear(m) (Go 1.21) |
| Tamanho | len(m) |
| Chaves ordenadas | slices.Sorted(maps.Keys(m)) (Go 1.23) |
| Copiar | maps.Clone(m) |
| Comparar | maps.Equal(a, b) |
Um map com valores struct{} também é o tipo set do Go; veja sets.
Erros comuns
- Escrever em um map nil. Sempre use
make, inclusive em campos map de structs. - Depender da ordem de iteração. Ordene as chaves.
- Usar
m[k] != 0como teste de presença. Use vírgula-ok. - Alterar um campo de struct por meio de
m[k].Field. Copie para fora e grave de volta, ou guarde ponteiros. - Compartilhar um map entre goroutines sem lock. O crash não pode ser recuperado.
Perguntas frequentes
Como verificar se uma chave existe em um map em Go?
Use a forma de busca com dois valores: v, ok := m[key]. ok é true quando a chave está presente e false quando não está, caso em que v é o valor zero. Ler só m[key] não diferencia uma chave ausente de uma chave guardada com o valor zero.
Por que a ordem de iteração de um map em Go é aleatória?
A linguagem não define uma ordem, e o runtime de propósito começa cada range em uma posição aleatória, para que os programas não passem a depender de uma ordem. Para percorrer na ordem das chaves, junte e ordene as chaves: for _, k := range slices.Sorted(maps.Keys(m)) (Go 1.23).
Como obter todas as chaves de um map em Go?
Desde o Go 1.23, maps.Keys(m) devolve um iterador; transforme-o em slice com slices.Collect(maps.Keys(m)), ou em um slice ordenado com slices.Sorted(maps.Keys(m)). Antes do 1.23, faça um laço for k := range m e acrescente cada chave a um slice.
Por que escrever em um map causa panic com "assignment to entry in nil map"?
A variável do map foi declarada, mas o map nunca foi criado: var m map[string]int é nil. Ler de um map nil devolve valores zero, mas escrever causa panic. Crie o map antes com m = make(map[string]int) ou com um literal m := map[string]int{}. Um campo map dentro de uma struct precisa da mesma inicialização.
Maps em Go são seguros para uso concorrente?
Não. Escritas concorrentes, ou uma escrita concorrente com leituras, podem derrubar o programa com fatal error: concurrent map writes, que o recover não consegue capturar. Proteja o map com um sync.Mutex ou sync.RWMutex, ou use sync.Map nos casos específicos para os quais ele foi feito.