The recover()
is a built-in function in Go that stops the program abort sequence invoked by the call of the panic()
function. It restores the normal execution of the application and allows for handling the error passed to the panic()
. In other words, recover()
“catches” panic
-type errors and allows you to handle them instead of terminating the application.
Panic and Defer
The recover()
works closely with the panic()
function and the defer
statement.
The panic()
function is used in Go to report an unrecoverable state in the application that prevents it from running any longer. In general, there are two types of sources of such a state in an application:
- Lack of access to resources necessary for the application to run. For example, a database connection error in an application that updates data in the database causes the application to be unable to continue running. In this case, you should either wait for access or explicitly terminate the application with an unrecoverable error using the
panic()
function. - Programming bugs causing the runtime errors, such as indexing a slice out of bounds. They automatically trigger the
panic()
and terminate the app.
A toy example showing how the panic()
function works for the case when the application lacks access to resources:
|
|
Output:
panic: error while connecting to db
goroutine 1 [running]:
main.main()
main.go:15 +0x49
exit status 2
In the example, the connectToDB()
function always returns an error that we use as an argument to the panic()
. As a result, the last line of the main()
, printing the "connected to db"
string is never executed because the program starts the panicking sequence earlier, which results in logging the error to standard output and terminating the application.
To define what the panicking sequence is and how it works, we must first learn more about the defer
statement.
The defer
statement ensures that a function is executed after the surrounding function returns (the one in which defer
was called). It does not matter whether the surrounding function ended without error or was interrupted by a panic. The defer
guarantees that the function following this keyword will be executed.
Look at the example:
|
|
As you can see, we call a sequence of three functions. The main()
function calls foo()
, which then calls bar()
, and the bar()
calls baz()
. In each of them, we print out the string "Hello <function-name>"
and in the first line declare the deferred function, which should print the string "Bye <function-name>"
. After each of the three functions call, we print the string "After <function-name>"
. Moreover, the baz()
invokes a panic with the string "panic in baz"
.
The program prints the following result to the output:
Hello foo
Hello bar
Hello baz
Bye baz
Bye bar
Bye foo
panic: panic in baz
goroutine 1 [running]:
main.baz()
main.go:20 +0xb8
main.bar()
main.go:14 +0xaa
main.foo()
main.go:8 +0xaa
main.main()
main.go:24 +0x17
exit status 2
In the beginning, everything works as expected - the foo()
, bar()
, and baz()
functions are executed one by one and print out the "Hello <function-name>"
messages. Inside the baz()
function, there is a panic, but it is not immediately visible in the output. Instead, the baz()
, bar()
and foo()
calls the deferred functions in sequence omitting the "After <function-name>"
string printing.
That is how the panic()
works - it starts the panicking sequence by immediately stopping the execution of the current function and starting to unwind the current goroutine function stack, running any deferred functions along the way. If this unwinding reaches the top of the stack, the panic is logged, and the program dies.
The diagram below shows how the panic()
function and defer
statement work in our example:
So, as a result of the panicking sequence, we can see the calls to defer
statement functions that print the "Bye <function-name>"
messages. During the panicking, the program do not call any other function than in the defer
statement - it did not print out the "After <function-name>"
strings. The panic itself is logged at the end when it reaches the top of the function stack. Because of that, you can see this log and stack trace as the last message in the output.
Of course, as we mentioned at the beginning, the defer
statement works regardless of whether panic occurs in the program or not. To check this, remove panic()
from the app and trace the output. Naturally, this also works the other way around, meaning you also do not have to use defer
when calling panic()
.
Recover from a Panic
Sometimes you may want the panicking to abort, the application to return to normal execution, and the whole sequence of unwinding the goroutine function stack and terminating the application to stop. This is what the built-in recover()
function is for.
The recover()
restores the normal execution of the program by stopping the panicking sequence. It returns the error value passed to the call of the panic()
, or nil
if the goroutine is not panicking.
The
panic()
can take any value as an argument, so therecover()
also returns the value of typeany
. Since Go 1.18,any
is an alias forinterface{}
.
The recover()
is useful in any case, where a single panic should not terminate the entire program. For example, a critical error in one of the web server client connections should not crash the server app.
It is also used for handling errors in the recursive function stack. If an error occurs at some level of a call to such a function, it usually needs to be handled at the top. To do this, report the panic to unwind the stack to the top-level function call and then recover()
from the panic to handle the error.
When you store your application logs, the recover()
can also be used to catch a panic and save the panic message to the storage.
Since you already know what the recover()
is and how it works, it’s time to show an example how to use it.
The following program is the same as the previous one except that this time we recover from the panic in the baz()
function:
|
|
Output:
Hello foo
Hello bar
Hello baz
Bye baz
RECOVERED: panic in baz
After baz
Bye bar
After bar
Bye foo
After foo
In the first line of the baz()
there is a deferred panic recovery function recoverPanic()
which checks whether the recover()
returns a non-nil result. If so, then the panic argument is printed to the standard output with the prefix RECOVERED:
.
In the output you can see that the application has indeed recovered the panic, as it returns to normal execution and prints the strings "After baz"
, "After bar"
, and "After foo"
.
Our sample application now looks like this in the diagram:
You may wonder why we called the recover()
in a deferred function in baz()
. Well, this is the first rule when using the recover()
function:
recover()
only works in a deferred function.This is because during the returning to the caller in panicking sequence, the only code that runs is deferred functions, so the recover()
will not run elsewhere.
The second rule is:
Look at the example:
|
|
Output:
deferred from bar
panic: panic in bar
goroutine 6 [running]:
main.bar()
recover/main.go:14 +0x73
main.main.func1()
recover/main.go:20 +0x17
created by main.main
recover/main.go:19 +0x45
exit status 2
In the main()
, we declared a call to the deferred function barRecover()
, which recovers from a panic. Then, we called the bar()
function that panics in a new separate goroutine. As you can see in the output, the panic is not recovered since it was invoked by a new goroutine, and the recover()
was in the main goroutine. In the diagram, it looks like this:
When you replace the bar()
call in a new goroutine:
|
|
with a regular call:
|
|
then the panic will be recovered and the output you will see will look like this:
deferred from bar
RECOVER: panic in bar
As a rule of thumb, remember this scheme and that you can only call recover()
in the same goroutine as panic()
. This will save you a lot of debugging time.
Conclusion
The recover()
function is a great mechanism in Go to avoid killing the application when unrecoverable errors occur and handle them so that the application can continue to run. In summary, you should remember the 3 most important things about this function: