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,
panic-type errors and allows you to handle them instead of terminating the application.
Panic and Defer
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
- 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:
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.
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
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
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
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.
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
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.
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
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
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 bar", and
Our sample application now looks like this in the diagram:
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:
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
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
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: