Iteração sobre map em Go: 5 erros fatais e como evitá-los

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Iteração sobre map em Go: como não pisar em rastelos (e não perder dados)

\n\n

Se você já programou em Go, certamente já encontrou o map — uma das estruturas de dados mais poderosas e traiçoeiras. Parece simples: pegue o range, percorra as chaves, obtenha os valores. Mas o diabo, como sempre, está nos detalhes. A pergunta "Iterating over all the keys of a map" no Stack Overflow acumulou milhares de visualizações, e não é por acaso.

\n\n

Hoje vamos analisar os principais erros ao iterar sobre map em Go, que até desenvolvedores experientes cometem. Você descobrirá por que a ordem de iteração é uma loteria, como trabalhar com map em goroutines com segurança e o que fazer se o map for nil. Vamos lá!

\n\n

Erro nº1: Esperar uma ordem (ou "Onde estão minhas chaves?")

\n\n

A armadilha mais comum é acreditar que o range retornará as chaves na mesma ordem em que foram adicionadas. Em Go, isso não acontece. A ordem de iteração sobre um map é fundamentalmente não determinística.

\n\n

Veja este código:

\n\n
package main\n\nimport "fmt"\n\nfunc main() {\n    m := map[string]int{\n        "apple":  1,\n        "banana": 2,\n        "cherry": 3,\n    }\n\n    for i := 0; i < 3; i++ {\n        fmt.Println("Iteração", i+1)\n        for k, v := range m {\n            fmt.Printf("  %s: %d\\n", k, v)\n        }\n    }\n}\n
\n\n

Provavelmente, cada execução produzirá uma ordem diferente. Isso não é um bug, é uma funcionalidade: o Go randomiza intencionalmente a ordem para que os desenvolvedores não se acostumem com ela e não criem lógica baseada em uma sequência aleatória.

\n\n

Como evitar?

\n
    \n
  • Não confie na ordem — esta é a regra principal.
  • \n
  • Se a ordem for importante, use um []string para armazenar as chaves separadamente e ordene-as:
  • \n
\n\n
package main\n\nimport (\n    "fmt"\n    "sort"\n)\n\nfunc main() {\n    m := map[string]int{"apple": 1, "banana": 2, "cherry": 3}\n\n    keys := make([]string, 0, len(m))\n    for k := range m {\n        keys = append(keys, k)\n    }\n    sort.Strings(keys)\n\n    for _, k := range keys {\n        fmt.Printf("%s: %d\\n", k, m[k])\n    }\n}\n
\n\n

Dica: Se precisar preservar a ordem de inserção, considere a biblioteca github.com/elliotchance/orderedmap ou crie seu próprio wrapper baseado em duas estruturas (map + slice).

\n\n

Erro nº2: Iteração com escrita simultânea (data race)

\n\n

Em Go, map não é seguro para acesso concorrente. Se uma goroutine lê (itera) um map enquanto outra escreve nele ao mesmo tempo, você receberá um fatal error: concurrent map iteration and map write.

\n\n

Exemplo de erro fatal:

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    m := make(map[int]int)\n\n    go func() {\n        for i := 0; i < 100; i++ {\n            m[i] = i\n        }\n    }()\n\n    go func() {\n        for k, v := range m {\n            fmt.Println(k, v)\n        }\n    }()\n\n    time.Sleep(time.Second)\n}\n
\n\n

Este código falhará com pânico. E nem sempre — a condição de corrida pode aparecer apenas na milésima execução.

\n\n

Como evitar?

\n\n

Existem três maneiras comprovadas:

\n\n
    \n
  1. sync.Mutex — mutex clássico para sincronizar o acesso:
  2. \n
\n\n
package main\n\nimport (\n    "fmt"\n    "sync"\n)\n\ntype SafeMap struct {\n    mu sync.Mutex\n    m  map[int]int\n}\n\nfunc (s *SafeMap) Set(key, value int) {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    s.m[key] = value\n}\n\nfunc (s *SafeMap) Iterate(f func(key, value int) bool) {\n    s.mu.Lock()\n    defer s.mu.Unlock()\n    for k, v := range s.m {\n        if !f(k, v) {\n            break\n        }\n    }\n}\n\nfunc main() {\n    sm := SafeMap{m: make(map[int]int)}\n    sm.Set(1, 10)\n    sm.Set(2, 20)\n\n    sm.Iterate(func(k, v int) b                

Blogs

Book Recommendations