Maps em Go são estruturas de dados que associam chaves a valores, funcionando como tabelas hash ou dicionários em outras linguagens. Eles são extremamente úteis para armazenar e recuperar dados rapidamente com base em uma chave única. Nesta aula, você aprenderá a criar, acessar, verificar a existência de chaves, remover entradas e iterar sobre maps, além de entender a característica de ordem aleatória na iteração.

Diferente de slices, maps são referências implícitas: ao atribuir um map a outra variável ou passá-lo para uma função, ambas compartilham o mesmo dado subjacente. Por isso, é importante entender seu comportamento para evitar efeitos colaterais.

Criação e acesso

Para criar um map, usamos a sintaxe map[TipoChave]TipoValor. A chave deve ser um tipo comparável (como strings, inteiros, floats, booleanos ou structs com campos comparáveis). O valor pode ser qualquer tipo, incluindo outros maps ou slices.

Existem duas formas principais de criar um map: usando a função make ou uma literal de map. Com make, você especifica o tipo e, opcionalmente, a capacidade inicial. Com literais, você já pode inicializar com pares chave-valor.

package main

import "fmt"

func main() {
    // Criando um map vazio com make
    var m1 map[string]int        // nil map, não pode ser usado diretamente
    m1 = make(map[string]int)    // inicializa
    m1["chave1"] = 10
    fmt.Println(m1) // map[chave1:10]

    // Criando e inicializando com literal
    m2 := map[string]int{
        "a": 1,
        "b": 2,
        "c": 3,
    }
    fmt.Println(m2) // map[a:1 b:2 c:3]

    // Acessando valores
    valor := m2["a"]
    fmt.Println(valor) // 1

    // Acessando chave inexistente retorna valor zero do tipo
    zero := m2["z"]
    fmt.Println(zero) // 0
}

Note que acessar uma chave que não existe não causa erro; retorna o valor zero do tipo do valor. Isso pode ser enganoso, por isso o comma-ok idiom é importante.

Comma-ok idiom

Para verificar se uma chave realmente existe no map, usamos o comma-ok idiom. A atribuição de um map retorna dois valores: o valor (ou zero) e um booleano indicando se a chave estava presente.

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1, "b": 2}

    valor, ok := m["a"]
    if ok {
        fmt.Println("Chave 'a' existe com valor", valor)
    } else {
        fmt.Println("Chave 'a' não existe")
    }

    valor, ok = m["z"]
    if !ok {
        fmt.Println("Chave 'z' não existe, valor retornado:", valor) // 0
    }
}

Esse padrão é essencial para evitar confusão entre chave existente com valor zero e chave inexistente. Sempre que você espera que a chave possa não existir, use o comma-ok.

delete

A função embutida delete remove uma chave de um map. Sua sintaxe é delete(map, chave). Se a chave não existir, a operação é um no-op (não causa erro).

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3}
    fmt.Println("Antes:", m)

    delete(m, "b")
    fmt.Println("Depois de deletar 'b':", m)

    // Deletar chave inexistente não faz nada
    delete(m, "z")
    fmt.Println("Depois de tentar deletar 'z':", m)
}

Não há função para esvaziar um map completamente; você pode iterar e deletar todas as chaves, ou simplesmente atribuir um novo map vazio (se não houver outras referências).

Iteração (ordem aleatória)

Para iterar sobre um map, usamos range. A ordem de iteração não é garantida e pode variar entre execuções. Isso é intencional em Go para forçar o programador a não depender de uma ordem específica.

package main

import "fmt"

func main() {
    m := map[string]int{"a": 1, "b": 2, "c": 3, "d": 4}

    for chave, valor := range m {
        fmt.Printf("%s -> %d\n", chave, valor)
    }

    // Se quiser apenas as chaves:
    for chave := range m {
        fmt.Println("Chave:", chave)
    }
}

Se você precisar de uma ordem fixa, pode coletar as chaves em um slice, ordená-lo e iterar sobre ele.

package main

import (
    "fmt"
    "sort"
)

func main() {
    m := map[string]int{"b": 2, "a": 1, "c": 3}
    chaves := make([]string, 0, len(m))
    for k := range m {
        chaves = append(chaves, k)
    }
    sort.Strings(chaves)
    for _, k := range chaves {
        fmt.Printf("%s -> %d\n", k, m[k])
    }
}

Lembre-se: maps não são seguros para concorrência. Se você precisar acessar um map de múltiplas goroutines, use sincronização (como sync.Mutex) ou sync.Map.

Boas práticas

  • Inicialize maps com make ou literais para evitar pânico ao tentar escrever em um map nil.
  • Sempre use comma-ok ao acessar chaves que podem não existir.
  • Não confie na ordem de iteração; se precisar de ordem, ordene as chaves.
  • Para maps aninhados, lembre-se de inicializar os maps internos separadamente.
  • Evite usar maps quando a ordem importa; considere slices de structs.

Referências

Exercícios

  1. Crie um map de string para int com as chaves "Go", "Java", "Python" e valores 1, 2, 3 respectivamente. Imprima o valor associado a "Go".

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        linguagens := map[string]int{
            "Go":     1,
            "Java":   2,
            "Python": 3,
        }
        fmt.Println(linguagens["Go"]) // 1
    }
    
  2. Dado o map do exercício anterior, verifique se a chave "Ruby" existe usando comma-ok. Imprima uma mensagem adequada.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        linguagens := map[string]int{"Go": 1, "Java": 2, "Python": 3}
        if _, ok := linguagens["Ruby"]; ok {
            fmt.Println("Ruby existe")
        } else {
            fmt.Println("Ruby não existe")
        }
    }
    
  3. Remova a chave "Java" do map do exercício anterior e imprima o map resultante.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        linguagens := map[string]int{"Go": 1, "Java": 2, "Python": 3}
        delete(linguagens, "Java")
        fmt.Println(linguagens) // map[Go:1 Python:3]
    }
    
  4. Itere sobre o map do exercício 1 e imprima cada chave e valor em linhas separadas (não se preocupe com a ordem).

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        linguagens := map[string]int{"Go": 1, "Java": 2, "Python": 3}
        for chave, valor := range linguagens {
            fmt.Printf("%s: %d\n", chave, valor)
        }
    }
    
  5. Crie um map de string para slice de strings. Adicione três entradas e imprima o slice de uma delas.

    ✓ Resposta:
    package main
    
    import "fmt"
    
    func main() {
        categorias := map[string][]string{
            "frutas":  {"maçã", "banana"},
            "legumes": {"cenoura", "batata"},
            "grãos":   {"arroz", "feijão"},
        }
        fmt.Println(categorias["frutas"]) // [maçã banana]
    }