Menu

Maps em Golang: criar, verificar chaves, percorrer, ordenar e apagar

Maps em Go guardam pares chave-valor com busca rápida. Veja como criá-los, verificar se uma chave existe com vírgula-ok, apagar, percorrer (em ordem aleatória), ordenar as chaves, guardar structs e evitar o panic do map nil e das escritas concorrentes.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

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çãoCódigo
Criarm := map[string]int{} ou make(map[string]int)
Inserir ou atualizarm[k] = v
Ler (zero se ausente)v := m[k]
Verificar presençav, ok := m[k]
Apagardelete(m, k)
Remover tudoclear(m) (Go 1.21)
Tamanholen(m)
Chaves ordenadasslices.Sorted(maps.Keys(m)) (Go 1.23)
Copiarmaps.Clone(m)
Compararmaps.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] != 0 como 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.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR