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:
Saída:
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:
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:
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 nilEvite retornar ponteiros
nil
embrulhados em interfacesUse 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