O nil que não é nil: Entendendo interfaces em Go

Go tem uma proposta clara: simplicidade, legibilidade e segurança. Mas por trás dessa simplicidade, existem nuances que podem gerar comportamentos inesperados — principalmente quando trabalhamos com interfaces e nil.

Introdução

Go tem uma proposta clara: simplicidade, legibilidade e segurança. Mas por trás dessa simplicidade, existem nuances que podem gerar comportamentos inesperados — principalmente quando trabalhamos com interfaces e nil.

Se você já escreveu algo como if err != nil e tomou um susto porque entrou no bloco mesmo com um erro aparentemente “nulo”, esse post é pra você.

Como funciona uma interface em Go

Internamente, uma interface em Go é representada como uma estrutura com dois campos invisíveis:

  • Tipo dinâmico (type): qual é o tipo do valor armazenado.

  • Valor dinâmico (value): o valor em si.

Representação conceitual:

interface {
   type:  *MyStruct
   value: nil
}

Se qualquer um desses campos estiver preenchido, a interface não será igual a nil.

Caso clássico: interface com ponteiro nil

Vamos simular um caso simples, que passa despercebido por muitos:

type MyError struct{}

func (e *MyError) Error() string {
    return "houve um erro"
}

func retornaErro() error {
    var e *MyError = nil
    return e
}

func main() {
    err := retornaErro()
    if err == nil {
        fmt.Println("Sem erro")
    } else {
        fmt.Println("Erro:", err)
    }
}

Saída:

Erro: houve um erro

Mesmo que e seja nil, a interface error não é! Ela carrega um tipo (*MyError) e um valor (nil), o que a torna não-nula do ponto de vista da linguagem.

Isso vale para qualquer interface

var ptr *int = nil
var i interface{} = ptr

fmt.Println(i == nil) // false!


Por que isso é um problema real?

Porque essa armadilha aparece em situações comuns, como:

  • Retorno de erros (error)

  • Implementações com ponteiros (io.Reader, json.Marshaler, etc.)

  • Middlewares ou wrappers de bibliotecas

  • Código legível que não se comporta como esperado

E o pior: esse erro pode passar batido em testes se o nil for ignorado.

Como evitar esse comportamento

1. Sempre retorne nil puro se for pra indicar ausência

Errado:

func getErr() error {
    var e *MyError = nil
    return e
}

 

Certo:

func getErr() error {
    return nil
}

 

2. Cuidado ao inicializar variáveis interface{} com valores nulos

Evite:

var x *Something = nil
var i interface{} = x

 

Prefira:

var i interface{} = nil

 

Ou verifique explicitamente o conteúdo:

if ptr, ok := i.(*Something); ok && ptr == nil {
    fmt.Println("i contém um ponteiro nil")
}

 

3. Para funções que retornam interface{} ou error, use tipos concretos ou nil

Evite retornos assim:

func busca() interface{} {
    var obj *Coisa = nil
    return obj // armadilha
}

Prefira:

func busca() *Coisa {
    return nil // retorno claro, sem interface
}

 

4. Ferramentas de análise estática podem salvar seu dia

Use staticcheck — ele detecta esse tipo de padrão e avisa com mensagens como:

SA5009: return of a nil value of type *T in an interface

Como debugar interfaces suspeitas

Às vezes, só de olhar pro valor você não entende o que está acontecendo. Use esse padrão pra inspecionar:

fmt.Printf("Tipo: %T | Valor: %#v\n", minhaInterface, minhaInterface)

 

Utilitário de debug:

func InterfaceInfo(i interface{}) {
    if i == nil {
        fmt.Println("Interface é nil")
        return
    }
    fmt.Printf("Interface não é nil\n  Tipo: %T\n  Valor: %#v\n", i, i)
}

 

Entendendo em profundidade: interface{} vs tipos concretos

interface{} == nil

Só será verdadeiro se não houver tipo nem valor associado.

var i interface{} = nil // ok
if i == nil { // true

interface{} contendo (*T)(nil)

Será diferente de nil:

var t *T = nil
var i interface{} = t

if i == nil { // false!

 

Quando esse bug costuma aparecer na prática?

  • Em middlewares de erro que retornam ponteiros

  • Em testes com assert.Nil(err)

  • Em código legível que não faz o que promete

  • Em wrappers que embalam valores sem perceber

Conclusão

O sistema de tipos de Go é simples e poderoso, mas como tudo que lida com ponteiros e abstração, precisa de atenção. O nil em interfaces é uma das armadilhas mais traiçoeiras e silenciosas da linguagem.

Regras de ouro:

  • Interface só é nil se tipo e valor forem nil

  • Evite retornar ponteiros nil embrulhados em interfaces

  • Use ferramentas como staticcheck

  • Sempre teste explicitamente os tipos se necessário

 

Por hoje é só pessoal. Keep on coding. E lembrem-se: Don’t believe the hype

 

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Post Anterior

Testes de Concorrência com synctest no Go 1.24: Mais Precisão, Menos Sleep

Posts Relacionados

Instalando Go

Instalar o Go hoje em dia é muito simples. Porém uma busca por tutoriais na Internet pode te levar a conteúdo desatualizado. Nesse post mostro como fazer da maneira correta.
Leia Mais

Concorrência em Go com Goroutines

Descubra o poder das goroutines em Go para concorrência eficiente. Aprenda canais, select e tratamento de erros, além das diferenças com threads. Torne seus programas mais escaláveis e eficazes.
Leia Mais