nil pointer dereference em Go

O “runtime error: invalid memory address or nil pointer dereference” é um dos panics mais comuns em programas Go. Ele acontece quando você tenta acessar um campo, chamar um método ou desreferenciar um ponteiro que tem valor nil — ou seja, não aponta para nenhum endereço de memória válido.

Diferente de linguagens com exceções como Java (NullPointerException) ou C# (NullReferenceException), em Go esse erro causa um panic que, se não recuperado, derruba o programa inteiro.


A Mensagem de Erro

goroutine 1 [running]:
main.main()
    /app/main.go:12 +0x1e
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x47f68e]

Causas Comuns

1. Ponteiro Não Inicializado

Quando você declara um ponteiro sem inicializá-lo, ele é nil por padrão:

package main

import "fmt"

type Usuario struct {
    Nome  string
    Email string
}

func main() {
    var u *Usuario // u é nil

    // PANIC: nil pointer dereference
    fmt.Println(u.Nome)
}

2. Retorno nil Não Verificado

Funções que retornam ponteiros podem retornar nil em caso de erro:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("arquivo-inexistente.txt")
    // Se ignorar err, file pode ser nil!
    // PANIC ao tentar usar file
    fmt.Println(file.Name())
    _ = err
}

3. Map Não Inicializado com Structs

Maps retornam o zero value quando a chave não existe. Para ponteiros, isso é nil:

package main

import "fmt"

type Config struct {
    Valor string
}

func main() {
    configs := map[string]*Config{
        "prod": {Valor: "producao"},
    }

    // "dev" não existe no map — retorna nil
    dev := configs["dev"]

    // PANIC: nil pointer dereference
    fmt.Println(dev.Valor)
}

4. Interface com Valor nil

Uma interface pode conter um ponteiro nil, o que gera confusão:

package main

import "fmt"

type Logger interface {
    Log(msg string)
}

type FileLogger struct {
    Path string
}

func (f *FileLogger) Log(msg string) {
    fmt.Printf("[%s] %s\n", f.Path, msg)
}

func getLogger() Logger {
    var fl *FileLogger // nil
    return fl          // Interface não-nil contendo ponteiro nil!
}

func main() {
    logger := getLogger()

    // logger != nil (interface tem tipo, mas valor nil)
    if logger != nil {
        // PANIC: o valor dentro da interface é nil
        logger.Log("teste")
    }
}

5. Slice de Ponteiros

Quando você tem um slice de ponteiros e algum elemento é nil:

package main

import "fmt"

type Pedido struct {
    ID    int
    Total float64
}

func main() {
    pedidos := []*Pedido{
        {ID: 1, Total: 99.90},
        nil, // Elemento nil!
        {ID: 3, Total: 45.00},
    }

    for _, p := range pedidos {
        // PANIC quando p é nil
        fmt.Printf("Pedido %d: R$ %.2f\n", p.ID, p.Total)
    }
}

Como Resolver

Solução 1: Sempre Verificar nil

A forma mais direta — verifique antes de usar:

package main

import "fmt"

type Usuario struct {
    Nome string
}

func buscarUsuario(id int) *Usuario {
    if id == 1 {
        return &Usuario{Nome: "Alice"}
    }
    return nil
}

func main() {
    u := buscarUsuario(2)

    // Verifica nil antes de acessar
    if u != nil {
        fmt.Println(u.Nome)
    } else {
        fmt.Println("Usuário não encontrado")
    }
}

Solução 2: Tratar Erros Antes de Usar o Resultado

Siga o padrão idiomático de tratamento de erros em Go:

package main

import (
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("config.json")
    if err != nil {
        fmt.Println("Erro:", err)
        return // Sai antes de usar file
    }
    defer file.Close()

    // Seguro: file não é nil aqui
    fmt.Println("Arquivo:", file.Name())
}

Solução 3: Inicializar com Valor Padrão

Use construtores ou valores padrão para evitar ponteiros nil:

package main

import "fmt"

type Config struct {
    Host string
    Port int
}

// Construtor que nunca retorna nil
func NewConfig() *Config {
    return &Config{
        Host: "localhost",
        Port: 8080,
    }
}

func main() {
    cfg := NewConfig() // Nunca nil
    fmt.Printf("%s:%d\n", cfg.Host, cfg.Port)
}

Solução 4: Verificar Maps com ok

Use o idioma de dois valores para maps:

package main

import "fmt"

type Config struct {
    Valor string
}

func main() {
    configs := map[string]*Config{
        "prod": {Valor: "producao"},
    }

    // Verifica se a chave existe
    if dev, ok := configs["dev"]; ok {
        fmt.Println(dev.Valor)
    } else {
        fmt.Println("Configuração 'dev' não encontrada")
    }
}

Solução 5: Usar recover para Panics Inesperados

Em servidores HTTP e sistemas que não podem cair, use recover:

package main

import "fmt"

func processarSeguro(dados interface{}) (resultado string, err error) {
    defer func() {
        if r := recover(); r != nil {
            err = fmt.Errorf("panic recuperado: %v", r)
        }
    }()

    // Código que pode causar panic
    m := dados.(map[string]string)
    return m["chave"], nil
}

func main() {
    resultado, err := processarSeguro(nil)
    if err != nil {
        fmt.Println("Erro:", err)
        return
    }
    fmt.Println(resultado)
}

Técnicas de Debug

Stack Trace

Quando um nil pointer dereference acontece, Go imprime o stack trace completo. Leia de baixo para cima para encontrar a causa:

goroutine 1 [running]:
main.processarPedido(0x0)           # <-- ponteiro 0x0 = nil!
    /app/handlers.go:45 +0x1e
main.main()
    /app/main.go:12 +0x3a

Delve Debugger

Use o Delve para depurar interativamente:

# Instalar
go install github.com/go-delve/delve/cmd/dlv@latest

# Executar com debugger
dlv debug .

Consulte nosso guia de profiling e performance para mais técnicas de debug.


Dicas para Evitar Este Erro

  1. Prefira valores a ponteiros — se a struct é pequena, passe por valor ao invés de ponteiro. Sem ponteiro, sem nil.

  2. Siga o padrão if err != nil — nunca ignore o retorno de erro. Veja tratamento de erros em Go.

  3. Use construtores (New…) — funções como NewConfig() garantem inicialização correta. Veja clean architecture em Go para padrões de projeto.

  4. Inicialize maps com makevar m map[string]int é nil. Use m := make(map[string]int).

  5. Habilite go vet e lintersstaticcheck e golangci-lint detectam possíveis nil dereferences.

  6. Escreva testestestes unitários e testes de tabela com casos nil previnem regressões.

Vale notar que linguagens como Rust eliminam nil pointer dereferences em tempo de compilação usando Option<T>, e Kotlin aborda o problema com null safety nativo no sistema de tipos.


Erros Relacionados