Menu

panic e recover em Go: panics em tempo de execução e quando usar panic

Um panic interrompe a execução normal e desenrola a pilha, executando as chamadas adiadas. Veja o que causa panics, como o recover em uma função adiada interrompe um, as mensagens de erro de runtime que você vai ver e quando causar panic é a decisão certa.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Como é um panic

Um panic interrompe a função atual, executa as suas chamadas adiadas e depois faz o mesmo em quem a chamou, e assim por diante subindo a pilha. Se ele chegar ao topo da goroutine, o programa cai.

Este programa sai com status 2. A saída é:

before
deferred in main: still runs
panic: runtime error: index out of range [5] with length 3

goroutine 1 [running]:
main.main()
	/tmp/main.go:11 +0x...
exit status 2

A chamada adiada executou antes do relatório de falha. O trace informa a goroutine, a função e a linha, o que normalmente basta para encontrar o bug.

Panics comuns em tempo de execução

MensagemCausa
index out of range [5] with length 3índice de slice, array ou string além do fim
slice bounds out of range [:7] with capacity 5fatiar além da capacidade
invalid memory address or nil pointer dereferenceler um campo ou chamar algo por meio de um ponteiro nil
assignment to entry in nil mapescrever em um map que nunca foi criado
interface conversion: interface {} is int, not stringtype assertion de um valor para o tipo errado
integer divide by zerodivisão inteira ou módulo por 0 (floats dão +Inf ou NaN)
close of closed channel, send on closed channeluso errado de channel
all goroutines are asleep - deadlock!todas as goroutines bloqueadas (um erro fatal, não um panic)

Cada um deles é um bug no programa, não uma condição a tratar. A correção é uma verificação de limites, uma verificação de nil, um make ou uma assertion vírgula-ok, não um recover.

Recuperando

recover() interrompe um panic. Ele só funciona quando chamado diretamente dentro de uma função adiada, porque funções adiadas são o único código que executa enquanto um panic desenrola a pilha.

Saída:

5 <nil>
0 recovered: runtime error: integer divide by zero
program continues

O que aconteceu na segunda chamada:

  1. a / b causou panic.
  2. A closure adiada executou, e recover() devolveu o valor do panic (um runtime.Error).
  3. O desenrolar parou. safeDivide retornou normalmente para o main, com o resultado nomeado err definido pela closure.

O resultado nomeado é o que permite à função adiada devolver um erro. Sem ele, a função devolve os seus valores zero. A página de defer mostra como closures adiadas modificam resultados.

recover() devolve nil quando não há panic, então a verificação if r != nil torna a função adiada inofensiva no caminho normal. Chamado fora de uma função adiada, ou em uma função chamada pela função adiada, recover devolve nil e não faz nada.

panic com o seu próprio valor

panic aceita qualquer valor. Um erro ou uma string é o típico.

Recupere o que você espera e cause panic de novo em todo o resto. Engolir todo panic esconde bugs reais.

Desde o Go 1.21, panic(nil) é transformado em um *runtime.PanicNilError, então recover() devolver nil agora significa com certeza "não houve panic".

Panics em goroutines

O recover só captura panics na própria goroutine. Um panic em qualquer goroutine sem recover mata o processo inteiro, incluindo o main e todas as outras goroutines.

As duas linhas dos workers podem sair em qualquer ordem; main finished sempre vem por último. Um defer recover() no main não teria salvado o programa do segundo worker. É por isso que servidores HTTP recuperam por requisição: o net/http recupera panics na goroutine de cada handler, registra em log e fecha aquela conexão, para que uma requisição ruim não derrube o servidor.

Algumas falhas são erros fatais, não panics, e não podem ser recuperadas de jeito nenhum: concurrent map writes, falta de memória e o all goroutines are asleep do detector de deadlock.

Quando causar panic é a decisão certa

A regra geral do Go: devolva erros para tudo o que pode dar errado em tempo de execução, e cause panic só por erros do programador. Na prática, panic é apropriado quando:

  • Uma invariante é quebrada. Um switch sobre o seu próprio enum chega a um case que não pode acontecer. Continuar corromperia dados.
  • Um helper Must recebe uma entrada constante inválida. regexp.MustCompile, template.Must e uuid.MustParse envolvem uma função que devolve erro e causam panic em caso de falha. Use-os para valores conhecidos em tempo de compilação, normalmente variáveis de nível de pacote, em que uma falha significa que o código-fonte está errado:
var emailRE = regexp.MustCompile(`^[^@\s]+@[^@\s]+$`)
  • A inicialização não pode continuar. Falta uma configuração obrigatória no main. Mesmo aqui, imprimir o erro e chamar os.Exit(1) costuma ser mais limpo que um stack trace.

Panic é a ferramenta errada para:

  • Falhas esperadas: entrada inválida do usuário, um arquivo ausente, um timeout. Devolva um error; veja tratamento de erros.
  • Controle de fluxo: usar panic e recover como exceções em uma árvore de chamadas grande deixa o código difícil de acompanhar. A biblioteca padrão faz isso internamente em alguns lugares (o encoder do encoding/json), sempre recuperando antes de retornar, para que nenhum panic escape do pacote.
  • APIs de bibliotecas: uma biblioteca que causa panic com entrada inválida obriga todo código que a chama a acrescentar recovers. Devolva um erro.

Erros comuns

  • Chamar recover fora de uma função adiada. Ele devolve nil.
  • Recuperar no main o panic de uma goroutine. Cada goroutine precisa do seu.
  • Engolir todos os panics. Registre com a pilha (debug.Stack() de runtime/debug) e cause panic de novo no que você não esperava.
  • Usar recover para lidar com escritas em map nil ou índices fora da faixa. Corrija o bug.

Perguntas frequentes

O que é um panic em Go?

Uma falha em tempo de execução que interrompe o fluxo normal da goroutine atual. O Go executa as chamadas adiadas de cada função da pilha, da mais interna para fora, e, se nada o recuperar, o programa imprime o valor do panic e um stack trace e sai com status 2. Panics vêm de bugs (índice fora da faixa, desreferência de ponteiro nil, escrita em map nil) ou de uma chamada explícita panic(v).

Como se recuperar de um panic em Go?

Chame recover() dentro de uma função adiada: defer func() { if r := recover(); r != nil { ... } }(). Ele devolve o valor passado para panic e interrompe o desenrolar da pilha, então a função que o adiou retorna normalmente para quem a chamou. Chamado em qualquer outro lugar, recover devolve nil e não faz nada.

Dá para recuperar um panic de outra goroutine?

Não. O recover só interrompe um panic na goroutine em que executa. Um panic em uma goroutine que você iniciou, sem recover dentro dela, derruba o programa inteiro. Cada goroutine que pode causar panic precisa do seu próprio recover adiado.

Quando usar panic em vez de devolver um erro?

Para bugs e estados impossíveis, não para falhas esperadas. Entrada inválida, arquivos ausentes e erros de rede são erros. O panic é razoável quando uma invariante é quebrada, quando um helper Must recebe uma constante que deveria ser sempre válida (regexp.MustCompile) ou quando o programa não consegue nem iniciar. Bibliotecas não devem deixar panics escaparem pela sua API pública.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR