golang的panic和recover
What is Panic?
The idiomatic way of handling abnormal conditions in a Go program is using errors. Errors are sufficient for most of the abnormal conditions arising in the program.
But there are some situations where the program cannot continue execution after an abnormal condition. In this case, we use panic to prematurely terminate the program. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates. This concept will be more clear when we write an example program.
It is possible to regain control of a panicking program using recover which we will discuss later in this tutorial.
panic and recover can be considered similar to try-catch-finally idiom in other languages such as Java except that they are rarely used in Go.
When Should Panic Be Used?
One important factor is that you should avoid panic and recover and use errors where ever possible. Only in cases where the program just cannot continue execution should panic and recover mechanism be used.
There are two valid use cases for panic.
An unrecoverable error where the program cannot simply continue its execution.
One example is a web server that fails to bind to the required port. In this case, it’s reasonable to panic as there is nothing else to do if the port binding itself fails.A programmer error.
Let’s say we have a method that accepts a pointer as a parameter and someone calls this method using a nil argument. In this case, we can panic as it’s a programmer error to call a method with nil argument which was expecting a valid pointer.
Panic Example
The signature of the built-in panic function is provided below,
1 | func panic(interface{}) |
The argument passed to the panic function will be printed when the program terminates. The use of this will be clear when we write a sample program. So let’s do that right away.
We will start with a contrived example which shows how panic works.
1 | package main |
The above is a simple program to print the full name of a person. The fullName function in line no. 7 prints the full name of a person. This function checks whether the firstName and lastName pointers are nil in line nos. 8 and 11 respectively. If it is nil the function calls panic with a corresponding message. This message will be printed when the program terminates.
Running this program will print the following output,
1 | panic: runtime error: last name cannot be nil |
Let’s analyze this output to understand how panic works and how the stack trace is printed when the program panics.
In line no. 19 we assign Elon to firstName. We call fullName function with lastName as nil in line no. 20. Hence the condition in line no. 11 will be satisfied and the program will panic. When panic is encountered, the program execution terminates, the argument passed to the panic function is printed followed by the stack trace. Since the program terminates following the panic function call in line no. 12, the code in line nos. 13, 14, and 15 will not be executed.
This program first prints the message passed to the panic function,
1 | panic: runtime error: last name cannot be nil |
and then prints the stack trace.
The program panicked in line no. 12 of fullName function and hence,
1 | goroutine 1 [running]: |
will be printed first. Then the next item in the stack will be printed. In our case, line no. 20 where the fullName is called is the next item in the stack trace. Hence it is printed next.
1 | main.main() |
Now we have reached the top level function which caused the panic and there are no more levels above, hence there is nothing more to print.
One More Example
Panics can also be caused by errors that happen during the runtime such as trying to access an index that is not present in a slice.
Let’s write a contrived example which creates a panic due to out of bounds slice access.
1 | package main |
In the program above, in line no. 9 we are trying to access n[4] which is an invalid index in the slice. This program will panic with the following output,
1 | panic: runtime error: index out of range [4] with length 3 |
Defer Calls During a Panic
Let’s recollect what panic does. When a function encounters a panic, its execution is stopped, any deferred functions are executed and then the control returns to its caller. This process continues until all the functions of the current goroutine have returned at which point the program prints the panic message, followed by the stack trace and then terminates.
In the example above, we did not defer any function calls. If a deferred function call is present, it is executed and then the control returns to its caller.
Let’s modify the example above a little and use a defer statement.
1 | package main |
The only changes made are the addition of the deferred function calls in line nos. 8 and 20.
This program prints,
1 | deferred call in fullName |
When the program panics in line no. 13, any deferred function calls are first executed and then the control returns to the caller whose deferred calls are executed and so on until the top level caller is reached.
In our case, defer statement in line no. 8 of fullName function is executed first. This prints the following message.
1 | deferred call in fullName |
And then the control returns to the main function whose deferred calls are executed and hence this prints,
1 | deferred call in main |
Now the control has reached the top level function and hence the program prints the panic message followed by the stack trace and then terminates.
Recovering from a Panic
recover is a builtin function that is used to regain control of a panicking program.
The signature of recover function is provided below,
1 | func recover() interface{} |
Recover is useful only when called inside deferred functions. Executing a call to recover inside a deferred function stops the panicking sequence by restoring normal execution and retrieves the error message passed to the panic function call. If recover is called outside the deferred function, it will not stop a panicking sequence.
Let’s modify our program and use recover to restore normal execution after a panic.
1 | package main |
The recoverFullName() function in line no. 7 calls recover() which returns the value passed to panic function call. Here we are just printing the value returned by recover in line no. 9. recoverFullName() is being deferred in line no. 14 of the fullName function.
When fullName panics, the deferred function recoverName() will be called which uses recover() to stop the panicking sequence.
This program will print,
1 | recovered from runtime error: last name cannot be nil |
When the program panics in line no. 19, the deferred recoverFullName function is called which in turn calls recover() to regain control of the panicking sequence. The call to recover() in line no. 8 returns the argument passed to panic() and hence it prints,
1 | recovered from runtime error: last name cannot be nil |
After execution of recover(), the panicking stops and the control returns to the caller, in this case, the main function. The program continues to execute normally from line 29 in main since the panic has been recovered 😃. It prints returned normally from main followed by deferred call in main
Let’s look at one more example where we recover from a panic caused by accessing an invalid index of a slice.
1 | package main |
Running the above program will output,
1 | Recovered runtime error: index out of range [4] with length 3 |
From the output, you can understand that we have recovered from the panic.