Controlar com funções de adiamento, pane e recuperação
Agora, vamos ver alguns fluxos de controle que são exclusivos do Go: defer
, panic
e recover
. Cada uma dessas funções tem vários casos de uso. Exploraremos os casos de uso mais importantes aqui.
Função defer
No Go, uma instrução defer
adia a execução de uma função (incluindo qualquer parâmetro) até que a função que contém a instrução defer
seja concluída. Em geral, uma função é adiada quando se deseja evitar esquecer de tarefas, como fechar um arquivo ou executar um processo de limpeza.
Você pode adiar quantas funções desejar. As instruções defer
são executadas na ordem inversa, da última para a primeira.
Confira como esse padrão funciona executando o seguinte código de exemplo:
package main
import "fmt"
func main() {
for i := 1; i <= 4; i++ {
defer fmt.Println("deferred", -i)
fmt.Println("regular", i)
}
}
Esta é a saída do código:
regular 1
regular 2
regular 3
regular 4
deferred -4
deferred -3
deferred -2
deferred -1
Neste exemplo, observe que toda vez que fmt.Println("deferred", -i)
foi adiado, o valor de i
foi armazenado e a chamada de função foi adicionada a uma fila. Depois que a função main()
terminou de imprimir os valores de regular
, todas as chamadas adiadas foram executadas. Observe que a saída das chamadas adiadas está em ordem inversa (última entrada, primeira saída), pois elas são exibidas com base na fila.
Um caso de uso típico para a função defer
é fechar um arquivo depois que você terminar de usá-lo. Veja um exemplo:
package main
import (
"io"
"os"
"fmt"
)
func main() {
newfile, error := os.Create("learnGo.txt")
if error != nil {
fmt.Println("Error: Could not create file.")
return
}
defer newfile.Close()
if _, error = io.WriteString(newfile, "Learning Go!"); error != nil {
fmt.Println("Error: Could not write to file.")
return
}
newfile.Sync()
}
Depois de criar ou abrir um arquivo, você adiará a função .Close()
para evitar esquecer de fechar o arquivo depois de terminar.
Função panic
Os erros de runtime fazem com que um programa Go entre em pane, como ao tentar acessar uma matriz usando um índice fora dos limites ou ao desreferenciar um ponteiro nulo. Você também pode forçar um programa a entrar em pane.
A função interna panic()
interrompe o fluxo de controle normal em um programa Go. Ao usar uma chamada panic
, todas as chamadas de função adiadas são executados normalmente. O processo continua a pilha até que todas as funções sejam retornadas. O programa então falha com uma mensagem de log. A mensagem inclui todas as informações de erro e um rastreamento de pilha para ajudar você a diagnosticar a causa raiz do problema.
Ao chamar a função panic()
, é possível adicionar qualquer valor como argumento. Normalmente, uma mensagem de erro é enviada sobre o motivo do pânico.
Por exemplo, o código a seguir combina as funções panic
e defer
. Tente executar esse código para ver como o fluxo de controle é interrompido. Observe que os processos de limpeza ainda são executados.
package main
import "fmt"
func highlow(high int, low int) {
if high < low {
fmt.Println("Panic!")
panic("highlow() low greater than high")
}
defer fmt.Println("Deferred: highlow(", high, ",", low, ")")
fmt.Println("Call: highlow(", high, ",", low, ")")
highlow(high, low + 1)
}
func main() {
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
Esta é a saída:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
panic: highlow() low greater than high
goroutine 1 [running]:
main.highlow(0x2, 0x3)
/tmp/sandbox/prog.go:13 +0x34c
main.highlow(0x2, 0x2)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x1)
/tmp/sandbox/prog.go:18 +0x298
main.highlow(0x2, 0x0)
/tmp/sandbox/prog.go:18 +0x298
main.main()
/tmp/sandbox/prog.go:6 +0x37
Program exited: status 2.
Eis o que acontece quando o código é executado:
Tudo é executado normalmente. O programa imprime os valores altos e baixos passados para a função
highlow()
.Quando o valor de
low
é maior que o valor dehigh
, o programa entra em pane. A mensagem dePanic!
aparece. Neste ponto, o fluxo de controle é interrompido, e todas as funções adiadas começam a imprimir a mensagemDeferred...
.O programa falha, e você vê o rastreamento de pilha completo. A mensagem
Program finished successfully!
não aparece.
Uma chamada para a função panic()
geralmente é executada quando erros graves não são esperados. Para evitar uma falha do programa, você pode usar outra função chamada recover()
.
Função recover
Às vezes, você pode querer evitar uma falha do programa e, em vez disso, relatar o erro internamente. Ou talvez você queira limpar a bagunça antes de deixar o programa falhar. Por exemplo, talvez você queira fechar qualquer conexão com um recurso para evitar mais problemas.
O Go fornece a função interna recover()
para permitir que você retome o controle após uma pane. Você só chama recover
em uma função em que também está chamando defer
. Se você chamar a função recover()
, ela retornará nil
e não terá nenhum efeito em execução normal.
Tente modificar a função main
no código anterior para adicionar uma chamada à função recover()
, como esta:
func main() {
defer func() {
handler := recover()
if handler != nil {
fmt.Println("main(): recover", handler)
}
}()
highlow(2, 0)
fmt.Println("Program finished successfully!")
}
Ao executar o programa, a saída terá a seguinte aparência:
Call: highlow( 2 , 0 )
Call: highlow( 2 , 1 )
Call: highlow( 2 , 2 )
Panic!
Deferred: highlow( 2 , 2 )
Deferred: highlow( 2 , 1 )
Deferred: highlow( 2 , 0 )
main(): recover from panic highlow() low greater than high
Program exited.
Você percebeu a diferença da versão anterior? A principal diferença é que você não vê mais o erro de rastreamento de pilha.
Na função main()
, você adia uma função anônima na qual chama a função recover()
. Uma chamada para recover()
falha ao retornar nil
quando o programa está em pânico. É possível fazer algo aqui para limpar a bagunça, mas neste exemplo você simplesmente imprime algo.
A combinação das funções panic
e recover
é a maneira idiomática pela qual o Go trata as exceções. Outras linguagens de programação usam o bloco try/catch
. Já o Go prefere a abordagem que você explorou aqui.
Confira mais informações na proposta para adicionar uma função interna try
no Go.