O que o defer faz
defer empilha uma chamada de função em uma lista. Quando a função que a contém retorna, a lista executa em ordem inversa.
Saída:
start
end
deferred 3
deferred 2
deferred 1
A ordem é a última a entrar, a primeira a sair, como uma pilha. Isso combina com o jeito como os recursos se aninham: se você abre A e depois B, normalmente quer fechar B antes de A.
Limpeza ao lado da aquisição
O uso principal do defer é escrever a limpeza na linha logo depois da aquisição, para que nenhum caminho de retorno possa esquecê-la.
Repare na ordem: verifique o erro primeiro, depois adie. Se os.Open falhou, f é nil, e adiar f.Close() antes da verificação chamaria Close em um *os.File nil (o que devolve um erro que você nunca vê, e confunde quem lê).
O mesmo formato funciona para locks:
mu.Lock()
defer mu.Unlock()
Se o código entre eles causar panic, o mutex ainda é liberado.
Os argumentos são avaliados na hora
A função adiada e os seus argumentos são avaliados quando a instrução defer executa. Só a chamada em si espera.
Saída:
x is now 2
deferred closure reads: 2
deferred with argument: 1
fmt.Println("...", x) capturou o valor 1 na linha do defer. A closure não tem argumentos; ela lê x quando finalmente executa. Escolha a forma que corresponde ao que você quer registrar.
Isso também vale para receivers de métodos. defer t.Stop() avalia t na hora, então reatribuir t depois não muda qual valor será parado.
Um truque comum de medição de tempo usa essa regra de propósito:
func handle() {
defer trace("handle")() // trace runs now, the returned func runs at exit
// ...
}
trace("handle") é chamada na hora (ela pode imprimir "enter" e registrar o horário de início), e a função que ela devolve é a que fica adiada.
defer em um laço
Chamadas adiadas executam quando a função retorna, não no fim de cada iteração do laço. Em um laço sobre muitos arquivos, isso mantém todos os arquivos abertos até a função terminar.
for _, path := range paths {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() // all files stay open until the function returns
process(f)
}
Com milhares de caminhos, isso esgota os descritores de arquivo. Mova o corpo para uma função própria, para que cada defer execute a cada iteração:
Cada chamada a processFile fecha o seu arquivo antes de o próximo ser aberto. Um literal de função chamado na hora (func() { ... }()) funciona do mesmo jeito quando uma função auxiliar nomeada parece exagero.
Alterando valores de retorno
Uma closure adiada executa depois de a instrução return atribuir os resultados, e pode modificar resultados nomeados antes que quem chama os veja.
Isto imprime 10 e save failed: disk full. Com um resultado sem nome, uma função adiada ainda executa, mas não tem como mudar o que é devolvido.
Capturando o erro do Close
defer f.Close() joga fora o erro do Close. Para arquivos que você só lê, tudo bem. Para arquivos em que você escreveu, o Close pode reportar uma falha na gravação final, então o erro importa. Um resultado nomeado permite guardá-lo:
func writeReport(path string, data []byte) (err error) {
f, err := os.Create(path)
if err != nil {
return err
}
defer func() {
if cerr := f.Close(); cerr != nil && err == nil {
err = cerr
}
}()
_, err = f.Write(data)
return err
}
errors.Join(err, f.Close()) é uma alternativa mais curta quando você quer os dois erros reportados.
defer, panic e recover
Chamadas adiadas executam enquanto um panic desenrola a pilha. Esse é o único lugar em que recover tem efeito, e é assim que um servidor evita que uma requisição ruim derrube o processo. Os detalhes estão em panic e recover.
Chamadas adiadas não executam quando o programa sai por os.Exit ou log.Fatal. Se main adia uma limpeza e depois chama os.Exit(1), a limpeza é pulada.
Custo
Desde o Go 1.14, a maioria dos defers é gerada em linha pelo compilador (open-coded) e custa poucos nanossegundos. Usar defer em toda liberação de mutex e fechamento de arquivo é o estilo normal. Defers dentro de laços são a exceção: eles não podem ser gerados em linha e caem em um caminho mais lento, mais um motivo para mover o corpo de laços para funções.
Erros comuns
- Adiar antes de verificar o erro. Verifique o
errdoOpenprimeiro, depois façadefer Close. - Esperar limpeza a cada iteração de um laço. Defers executam na saída da função.
- Esperar que um argumento adiado veja mudanças posteriores. Os argumentos ficam fixos na linha do
defer. Use uma closure para ler na saída. - Contar com o defer junto com
os.Exit. Ele nunca executa.
Perguntas frequentes
O que o defer faz em Go?
defer f() agenda f() para executar quando a função que a contém retornar, seja normalmente, por um return antecipado ou por um panic. Ele serve para colocar a limpeza (fechar um arquivo, liberar um mutex) logo ao lado do código que adquiriu o recurso.
Em que ordem as chamadas adiadas executam em Go?
A última a entrar é a primeira a sair (LIFO). A chamada adiada mais recente executa primeiro. defer fmt.Println(1); defer fmt.Println(2) imprime 2 e depois 1.
Quando os argumentos de uma função adiada são avaliados?
Na hora, quando a instrução defer executa, e não quando a chamada roda. x := 1; defer fmt.Println(x); x = 2 imprime 1. Para ler o valor no momento da saída, adie uma closure: defer func() { fmt.Println(x) }().
O defer executa em um panic ou em os.Exit?
Chamadas adiadas executam enquanto um panic desenrola a pilha, e é por isso que recover funciona dentro delas. Elas não executam quando o programa chama os.Exit (ou log.Fatal, que o chama), e não executam em outras goroutines quando main retorna.