O que é Defer em Go?
A palavra-chave defer em Go adia a execução de uma chamada de função para o momento em que a função que a contém retorna. É um mecanismo poderoso para garantir que operações de limpeza — como fechar arquivos, liberar locks ou encerrar conexões — sejam executadas independentemente do caminho que a função tomar, incluindo em caso de erros.
Defer é uma das funcionalidades mais distintas de Go e resolve de forma elegante o problema de gerenciamento de recursos que em outras linguagens requer try/finally (Java/Python) ou RAII (C++/Rust).
Sintaxe básica
package main
import (
"fmt"
"os"
)
func main() {
arquivo, err := os.Open("dados.txt")
if err != nil {
fmt.Println("Erro:", err)
return
}
defer arquivo.Close() // será executado quando main() retornar
// ... processa o arquivo ...
fmt.Println("Processando arquivo...")
}
O defer arquivo.Close() garante que o arquivo será fechado quando main() terminar, não importa se a execução chega ao final normalmente ou retorna antes por causa de um erro. Isso elimina a chance de esquecer de fechar o recurso em algum caminho de erro.
Ordem de execução: LIFO (pilha)
Quando múltiplos defer são chamados na mesma função, eles são executados em ordem LIFO (Last In, First Out) — como uma pilha. O último defer registrado é o primeiro a executar:
func exemploOrdem() {
fmt.Println("início")
defer fmt.Println("primeiro defer")
defer fmt.Println("segundo defer")
defer fmt.Println("terceiro defer")
fmt.Println("fim da função")
}
Saída:
início
fim da função
terceiro defer
segundo defer
primeiro defer
Essa ordem LIFO faz sentido intuitivamente: recursos adquiridos por último devem ser liberados primeiro. Por exemplo, se você abre um arquivo e depois adquire um lock sobre ele, precisa liberar o lock antes de fechar o arquivo.
Avaliação dos argumentos
Uma característica importante: os argumentos de uma função defer são avaliados imediatamente, no momento em que o defer é declarado, não quando é executado:
func exemploAvaliacao() {
x := 10
defer fmt.Println("Valor de x:", x) // x=10 é capturado agora
x = 20
fmt.Println("x atual:", x)
}
Saída:
x atual: 20
Valor de x: 10
Para capturar o valor no momento da execução (não da declaração), use uma closure:
func exemploComClosure() {
x := 10
defer func() {
fmt.Println("Valor de x:", x) // captura a variável, não o valor
}()
x = 20
}
// Saída: Valor de x: 20
Casos de uso principais
Fechar arquivos e conexões
O uso mais comum de defer é garantir que recursos sejam liberados:
func processarArquivo(caminho string) error {
f, err := os.Open(caminho)
if err != nil {
return err
}
defer f.Close()
// Processa o arquivo...
scanner := bufio.NewScanner(f)
for scanner.Scan() {
// ...
}
return scanner.Err()
}
Liberar mutexes
Defer é essencial para evitar deadlocks ao trabalhar com goroutines e mutexes:
type ContadorSeguro struct {
mu sync.Mutex
valor int
}
func (c *ContadorSeguro) Incrementar() {
c.mu.Lock()
defer c.mu.Unlock() // garante que o unlock acontece mesmo com panic
c.valor++
// ... lógica complexa que pode falhar ...
}
func (c *ContadorSeguro) Valor() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.valor
}
Sem defer, cada caminho de retorno precisaria de um Unlock() explícito — muito fácil de esquecer e causar deadlock.
Medir tempo de execução
func medirTempo(nome string) func() {
inicio := time.Now()
return func() {
fmt.Printf("%s levou %v\n", nome, time.Since(inicio))
}
}
func operacaoLenta() {
defer medirTempo("operacaoLenta")()
time.Sleep(500 * time.Millisecond)
// ...
}
// Saída: operacaoLenta levou 500.123ms
Fechar conexões HTTP
func buscarDados(url string) ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close() // sempre feche o body!
return io.ReadAll(resp.Body)
}
Esquecer de fechar resp.Body é um dos vazamentos de recurso mais comuns em Go. O defer resolve isso de forma limpa.
Defer, Panic e Recover
Defer desempenha um papel central no mecanismo de recuperação de erros fatais em Go, trabalhando junto com panic e recover:
func operacaoSegura() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recuperado de panic:", r)
}
}()
fmt.Println("Executando operação...")
panic("algo deu muito errado!")
fmt.Println("Isso nunca executa")
}
func main() {
operacaoSegura()
fmt.Println("Programa continua normalmente")
}
Saída:
Executando operação...
Recuperado de panic: algo deu muito errado!
Programa continua normalmente
Importante: recover() só funciona dentro de uma função defer. Fora de defer, recover sempre retorna nil. Esse padrão é usado em servidores HTTP e middleware para evitar que um panic em uma goroutine derrube o servidor inteiro.
Cuidados com defer em loops
Defer dentro de loops pode causar vazamento de recursos, pois os defers só executam quando a função retorna, não quando a iteração termina:
// ERRADO — todos os arquivos ficam abertos até main retornar
func processarTodos(caminhos []string) {
for _, caminho := range caminhos {
f, err := os.Open(caminho)
if err != nil {
continue
}
defer f.Close() // só executa quando processarTodos retorna!
// ...
}
}
// CORRETO — extraia para uma função separada
func processarTodos(caminhos []string) {
for _, caminho := range caminhos {
processarUm(caminho)
}
}
func processarUm(caminho string) {
f, err := os.Open(caminho)
if err != nil {
return
}
defer f.Close() // executa ao final de cada chamada
// ...
}
Performance de defer
A partir do Go 1.14, o custo de defer foi significativamente reduzido para a maioria dos casos. Defers “inline” (que não envolvem loops ou condicionais complexos) têm overhead praticamente zero. Não evite defer por razões de performance — o ganho em legibilidade e segurança supera amplamente qualquer custo mínimo.
Perguntas Frequentes
Em que ordem os defers são executados?
Defers são executados em ordem LIFO (Last In, First Out), como uma pilha. O último defer registrado executa primeiro. Isso garante que recursos adquiridos por último sejam liberados primeiro, o que é o comportamento correto na maioria dos cenários — por exemplo, liberar um lock antes de fechar a conexão que ele protege.
Defer dentro de loop é perigoso?
Sim, pode causar problemas. Defers só executam quando a função retorna, não quando a iteração do loop termina. Se você abre arquivos em um loop com defer, todos os arquivos ficarão abertos até a função retornar, podendo esgotar descritores de arquivo. A solução é extrair o corpo do loop para uma função separada, onde o defer executará ao final de cada chamada.
Qual a diferença entre defer e finally?
Em linguagens como Java e Python, finally é um bloco dentro de try/catch que executa após a conclusão do bloco. Em Go, defer pode ser chamado em qualquer ponto da função, não apenas dentro de um bloco de tratamento de erros. Isso permite declarar a limpeza logo após a aquisição do recurso, tornando o código mais legível — o defer file.Close() fica logo depois do os.Open(), não em um bloco finally separado distante do código.
Posso retornar valores com defer?
Sim, mas apenas em funções com named return values (valores de retorno nomeados). O defer pode modificar o valor de retorno antes que a função efetivamente retorne:
func dobrar(x int) (resultado int) {
defer func() {
resultado *= 2
}()
return x // resultado = x, depois defer dobra
}
// dobrar(5) retorna 10
Esse padrão é usado na prática para adicionar informações extras a erros retornados ou para registrar o resultado em logs.