panic and recover
In the previous lesson defer was a to-do list by the door, read on the way out. Now picture a fire alarm. People leave the building floor by floor, but on each floor they go through the list by the door on their way out. Only someone whose list has the item "check and cancel the alarm" can cancel it.
Panic
In Go such an alarm is called a panic. Dividing an integer by zero while the program runs causes a panic. A panic interrupts the current function and every function that called it, but deferred calls still run on the way:
package main
import "fmt"
func divide(a, b int) int {
return a / b
}
func main() {
defer fmt.Println("deferred call")
fmt.Println(divide(6, 3)) // 2
fmt.Println(divide(1, 0)) // ✗
}panic: runtime error: integer divide by zeroLet's go through what the program prints:
divide(6, 3)returns2, and the program prints2.divide(1, 0)divides by zero, and a panic starts.- The program prints
deferred call. - Only after that does the panic message appear.
The panic rises from divide into main, runs the deferred calls of main and ends the program. This climb up the chain of calls is called stack unwinding.
Where a panic comes from
A panic starts in two ways. The value it starts with can be anything.
A runtime error
Go itself starts a panic when an operation cannot be performed. Dividing an integer by zero is such a case. The value of this panic is a runtime error, and the message begins with runtime error:, as in the example above.
Calling panic
You can also start a panic yourself with the built-in panic function. The parentheses hold the panic value, such as a string. After panic("no config") the program stops with the message panic: no config.
The recover function
The built-in recover function stops a panic. It follows three rules:
- it works only inside a deferred function;
- during a panic it returns the value passed to
panic, and the unwinding stops; - without a panic it returns
nil.
A deferred function runs during the unwinding, so only there does recover catch the panic. This is the "cancel the alarm" item on the list by the door. The function whose deferred call stopped the panic finishes at once, and the code that called it carries on.
func safeDivide(a, b int) (q int, ok bool) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered:", r)
}
}()
return a / b, true
}
func main() {
fmt.Println(safeDivide(6, 3)) // 2 true
fmt.Println(safeDivide(1, 0)) // 0 false
}Both results of a call can go straight into fmt.Println, with no variables in between: it prints them separated by a space. Let's go through both calls:
safeDivide(6, 3)divides without a panic,recoverreturnsnil, and2 trueis printed.safeDivide(1, 0)divides by zero, and the deferred function printsrecovered: runtime error: integer divide by zero.- Then
0 falseis printed. The statementreturn a / b, truenever completed, so the named results kept their zero values.
Here the fire-alarm picture ends. After a cancelled alarm, people go back to their floor and carry on working. But safeDivide does not return to the interrupted line: it finishes at once, and main gets its results as from an ordinary return and carries on from the next line.
An error instead of a panic
A deferred function can do more than print the panic: it can change the result. It sees the named results by name, and whatever it writes into them is what the function returns:
func safeDivide(a, b int) (q int, err error) {
defer func() {
if r := recover(); r != nil {
err = errors.New("division failed")
}
}()
return a / b, nil
}
func main() {
fmt.Println(safeDivide(6, 3)) // 2 <nil>
fmt.Println(safeDivide(1, 0)) // 0 division failed
}Let's go through the second call:
- Dividing by zero panics, and
return a / b, nilnever completes. - The deferred function catches the panic and writes an error into
err. safeDividereturnsqanderras they are:0anddivision failed.
This way a panic inside the function reaches the calling code as an ordinary error.
Where recover works
Interviewers like to ask this. The answer has three parts:
recoverworks only in a deferred function;- it has to be called directly in that function's body;
- anywhere else,
recoverreturnsniland does not stop the panic.
Panic or error
Failures come in two kinds, and each has its own tool:
- a panic—for errors that cannot happen in a correct program;
- an
errorvalue—for an expected failure, such as bad input: the function returns it.
Common mistake
recover is called directly in the function body, not in a deferred function:
func safeDivide(a, b int) int {
recover() // ✗
return a / b
}The call safeDivide(1, 0) ends the program with the same integer divide by zero panic. Here is why:
recoverruns at the start of the body, when there is no panic yet, and returnsnil.- The panic on the next line is no longer stopped by anyone.
Call recover inside defer func() { … }(), as in safeDivide above.
Key points
- A panic interrupts a function and rises up the chain of calls, running deferred calls on the way.
recoverstops a panic only when it is called in a deferred function; without a panic it returnsnil.- The function in which a panic was stopped finishes at once, and the code that called it carries on.
- A deferred function can write an error into a named result, and the caller gets it instead of the panic.
- Expected failures are returned as an
error; a panic is for what cannot happen in a correct program.
Subscribe to take the course
The course is free. Sign in with Telegram and subscribe to the channel to open every lesson.