Skip to main content


Maps


O Map é uma estrutura usada para guardar valores associados a uma chave. A ideia é parecida com um dicionário em Python ou um objeto simples em JavaScript, mas em Go o tipo da chave e o tipo do valor precisam estar definidos.

idades := map[string]int{
"Ana": 20,
"Carlos": 25,
}

Aqui, criamos um map onde a chave é string e o valor é int.

chave     valor
Ana -> 20
Carlos -> 25

Use map[string]any quando os dados são realmente dinâmicos, por exemplo:

  • JSON genérico
  • configurações variadas
  • dados vindos de API externa
  • estrutura temporária para testes


Por que maps existem


Os Slices são bons quando queremos guardar uma lista de valores em ordem.

nomes := []string{"Ana", "Carlos", "Bia"}

Mas às vezes queremos acessar um valor por uma chave, não por uma posição. Por exemplo, em vez de pegar a idade pela posição 0, 1 ou 2, queremos pegar pela chave "Ana".

idades["Ana"]

Isso deixa o código mais direto quando existe uma relação entre uma chave e um valor.



Criando um map com valores iniciais


package main

import "fmt"

func main() {
// Cria um map onde a chave é string
// e o valor é int.
idades := map[string]int{
"Ana": 20,
"Carlos": 25,
"Bia": 18,
}

fmt.Println(idades)
}

Resultado possível:

map[Ana:20 Bia:18 Carlos:25]

A ordem pode aparecer diferente, mas os Maps em Go não garantem ordem de iteração ou impressão.



Acessando um valor pela chave


Para acessar um valor, usamos a chave entre colchetes.

package main

import "fmt"

func main() {
idades := map[string]int{
"Ana": 20,
"Carlos": 25,
}

// Acessa o valor associado à chave "Ana".
fmt.Println(idades["Ana"])
}

Resultado:

20

A chave "Ana" aponta para o valor 20.



Adicionando um novo valor


Para adicionar um novo par chave/valor, usamos =.

package main

import "fmt"

func main() {
idades := map[string]int{
"Ana": 20,
}

// Adiciona uma nova chave no map.
idades["Carlos"] = 25

fmt.Println(idades)
}

Resultado possível:

map[Ana:20 Carlos:25]

Se a chave ainda não existe, o Go cria essa entrada no map.



Removendo um valor com delete


Para remover uma chave do map, usamos delete.

package main

import "fmt"

func main() {
idades := map[string]int{
"Ana": 20,
"Carlos": 25,
}

// Remove a chave "Carlos" do map.
delete(idades, "Carlos")

fmt.Println(idades)
}

Resultado:

map[Ana:20]

A função delete recebe dois argumentos:

delete(map, chave)

Se a chave não existir, nada acontece. Isso não gera erro.



Verificando se uma chave existe


Quando acessamos uma chave que não existe, o Go retorna o valor zero do tipo do valor.

package main

import "fmt"

func main() {
idades := map[string]int{
"Ana": 20,
}

fmt.Println(idades["Carlos"])
}

Resultado:

0

Isso pode confundir, porque 0 pode significar duas coisas:

a chave não existe
a chave existe e o valor dela é 0

Para resolver isso, usamos a forma com dois retornos:

valor, existe := idades["Carlos"]

Exemplo:

package main

import "fmt"

func main() {
idades := map[string]int{
"Ana": 20,
"Carlos": 0,
}

idade, existe := idades["Carlos"]

if existe {
fmt.Println("Idade encontrada:", idade)
} else {
fmt.Println("Usuário não encontrado")
}
}

Resultado:

Idade encontrada: 0

Aqui o valor é 0, mas a chave existe. Por isso o segundo retorno é importante. Esse padrão é muito comum em Go:

valor, ok := mapa[chave]

Muita gente usa o nome ok:

idade, ok := idades["Ana"]

O valor retornado será sempre o valor da chave mais um boolean (true ou false).



Criando map com make


Também podemos criar um map vazio usando make.

package main

import "fmt"

func main() {
// Cria um map vazio.
idades := make(map[string]int)

idades["Ana"] = 20
idades["Carlos"] = 25

fmt.Println(idades)
}

Esse formato é comum quando o map começa vazio e será preenchido depois.



Map vazio e map nil


Existe diferença entre map vazio e map nil. Um map criado com make pode receber valores:

package main

import "fmt"

func main() {
idades := make(map[string]int)

// Funciona.
idades["Ana"] = 20

fmt.Println(idades)
}

Já um map declarado sem inicialização começa como nil.

package main

import "fmt"

func main() {
var idades map[string]int

fmt.Println(idades == nil)
}

Resultado:

true

Um map nil pode ser lido, mas não pode receber novos valores.

package main

func main() {
var idades map[string]int

// Erro em tempo de execução.
// O map está nil, então não podemos adicionar valor.
// idades["Ana"] = 20

_ = idades
}

Para adicionar valores, inicialize com make ou com literal:

idades := make(map[string]int)

// ou:
idades := map[string]int{}


Tipo da chave


A chave de um map precisa ser de um tipo comparável, como string, int, bool e alguns outros podem ser usados como chave.

map[string]int
map[int]string
map[bool]string

Exemplo com int como chave:

package main

import "fmt"

func main() {
usuarios := map[int]string{
1: "Ana",
2: "Carlos",
}

fmt.Println(usuarios[1])
}

Resultado:

Ana

Os Slices não podem ser usados como chave de map, porque slices não são comparáveis diretamente.



Maps com any


O any significa que o valor pode ser de qualquer tipo.

package main

import "fmt"

func main() {
usuario := map[string]any{
"nome": "Ana",
"idade": 20,
"altura": 1.68,
"ativo": true,
}

fmt.Println(usuario["nome"])
fmt.Println(usuario["idade"])
fmt.Println(usuario["altura"])
fmt.Println(usuario["ativo"])
}

Nesse caso, a chave é sempre string, mas o valor pode ser string, int, float64, bool etc.



O cuidado com any


Quando usamos any, o Go deixa o valor entrar, mas depois ele não sabe automaticamente qual é o tipo exato para fazer operações.

package main

import "fmt"

func main() {
dados := map[string]any{
"idade": 20,
}

// Isso não funciona, porque 'dados["idade"]' é any, não int diretamente.
idade := dados["idade"] + 1

fmt.Println(dados["idade"])
}

Para usar como int, precisamos converter com type assertion.

package main

import "fmt"

func main() {
dados := map[string]any{
"idade": 20,
}

// Tenta pegar o valor como int.
idade, ok := dados["idade"].(int)

if !ok {
fmt.Println("idade não é int")
return
}

fmt.Println(idade + 1)
}

Resultado:

21


Verificando tipos diferentes


Quando o valor pode ter vários tipos, usamos switch de tipo.

package main

import "fmt"

func main() {
dados := map[string]any{
"nome": "Ana",
"idade": 20,
"altura": 1.68,
}

for chave, valor := range dados {
switch v := valor.(type) {
case string:
fmt.Println(chave, "é string:", v)
case int:
fmt.Println(chave, "é int:", v)
case float64:
fmt.Println(chave, "é float64:", v)
default:
fmt.Println(chave, "tem tipo desconhecido")
}
}
}


Quando evitar

Se os campos são conhecidos, uma struct normalmente é melhor. Em vez de:

usuario := map[string]any{
"nome": "Ana",
"idade": 20,
}

Prefira:

type Usuario struct {
Nome string
Idade int
}

Exemplo:

package main

import "fmt"

type Usuario struct {
Nome string
Idade int
}

func main() {
usuario := Usuario{
Nome: "Ana",
Idade: 20,
}

fmt.Println(usuario.Nome)
fmt.Println(usuario.Idade)
}

A struct é melhor quando os dados têm formato conhecido, porque o Go consegue verificar os tipos antes do programa rodar.


Sei que ainda não vimos sobre struct, veremos mais adiante no próximo capítulo.