21 The panic Function, the recover Function and the defer Statement - Part 1 #
In the previous two articles, I have explained in detail the error handling in the Go language and summarized the handling techniques and design patterns for error types and error values from two perspectives.
In this article, I will show you another way of handling errors in Go. Strictly speaking, it deals not with errors, but with exceptions, which are unexpected program exceptions.
Background knowledge: Runtime Panic #
This type of program exception is called a panic, which I translate as “runtime panic”. The word “panic” is a translation of “panic”, and the reason for adding the words “runtime” in front is because this kind of exception is only thrown during program execution.
Let’s take a specific example.
For example, in a Go program, there is a slice with a length of 5, which means that the indexes of the element values in the slice are 0
, 1
, 2
, 3
, 4
. However, in the program, I try to access an element value using index 5
, which is obviously an incorrect access.
The Go program, more precisely speaking, the Go language runtime system embedded in the program, will throw a “index out of range” panic when executing this line of code, indicating that you have gone out of bounds.
Of course, this is not just a hint. After the panic is thrown, if we don’t add any protection measures in the program, the program (or the process that represents it) will terminate after printing the details of the panic (hereinafter referred to as “panic details”).
Now, let’s take a look at what is included in these panic details.
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
/Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q0/demo47.go:5 +0x3d
exit status 2
The first line of this detail is “panic: runtime error: index out of range”. The meaning of “runtime error” is that this is a panic thrown by the runtime
code package. In this panic, there is a value of type runtime.Error
, which is an implementation of the error
interface and does some extension. The runtime
package has many implementations of this type.
In fact, the content on the right side of “panic: " in this panic detail is the string representation of the runtime.Error
value contained in this panic.
In addition, the panic detail generally includes the code execution information of the goroutine related to its cause. As mentioned above, “goroutine 1 [running]” indicates that there is a goroutine with ID 1
running when this panic is thrown.
Note that the ID here is not important because it is just a goroutine number given by the Go language runtime system internally, and we cannot obtain or modify it in the program.
Let’s take a look at the next line, “main.main()” indicates that the function wrapped by this goroutine is the main
function in the command source file, which means that this goroutine is the main goroutine. The following line specifies which line of code in this goroutine is being executed when this panic is thrown.
This includes the line number of this code in its source file and the absolute path of the source file. The “+0x3d” at the end of this line represents: the offset of the program counter relative to the entrance of its enclosing function. However, it is generally not very useful.
Finally, “exit status 2” indicates that my program ended with an exit status code of 2
. In most operating systems, as long as the exit status code is not 0
, it means that the program terminated abnormally. In Go, the exit status code for program termination due to panic is generally 2
.
In summary, from this panic detail above, we can see that the code that is the root cause of this panic is in line 5 of the file demo47.go and is included in the main
package (the package where the command source file is located).
So, my first question follows. My question is: What is the approximate process from the panic being thrown to the program terminating?
The typical answer to this question is as follows.
Let’s talk about the approximate process: a panic is intentionally or unintentionally thrown by a line of code in a function. At this point, the initial panic detail is established, and the control of the program immediately transfers from this line of code to the line of code that calls its enclosing function, which is the level above it in the call stack.
This also means that the execution of the function to which this line of code belongs is terminated. Immediately afterwards, the control does not linger here for a moment, and it immediately transfers to the calling code at the next higher level. The control propagates in this way, level by level, in the opposite direction of the call stack, until it reaches the top, which is the outermost function we wrote.
The outermost function here refers to the go
function, which is the main
function for the main goroutine. But the control does not stop there either, it is taken back by the Go language runtime system.
Then, the program crashes and terminates, and the process that carries the program’s execution dies and disappears. At the same time, during this control propagation process, the panic details are gradually accumulated and perfected, and they will be printed out before the program terminates.
Problem Analysis #
A panic can be triggered unintentionally (or carelessly) as an index out of range, as mentioned earlier. This type of panic is a genuine program exception that is unexpected. However, besides this, we can also intentionally trigger a panic.
The built-in function panic
in Go is specifically used to trigger a panic. The panic
function allows developers to report exceptions during program execution.
Note that this is completely different from returning an error value from a function. When a function returns a non-nil
error value, the caller of the function has the option to choose not to handle it, and the consequences of not handling it are often non-fatal.
By “non-fatal,” it means that it doesn’t render the program completely dysfunctional (or “dead”) or crash and terminate the execution (which is the “real death”).
However, when a panic occurs, if we don’t take any protective measures, the direct consequence is a program crash, as described earlier, which is obviously fatal.
To clearly demonstrate the process described in the answers, I have written a file called demo48.go. You can take a look at the code in it, try running it, and understand the meaning of the contents it prints.
Here’s another point to note. The details of the panic are gradually accumulated and refined during the propagation of control, and the control is propagated back up the call stack.
Therefore, in the execution information of the code for a particular goroutine, the information of the bottom of the call stack appears first, followed by the information of the caller one level higher, and so on, until finally, the information at the top of the call stack appears.
For example, if the main
function calls the caller1
function, and the caller1
function calls the caller2
function, then the execution information of the code in the caller2
function will appear first, followed by the execution information of the code in the caller1
function, and finally the information of the main
function.
goroutine 1 [running]:
main.caller2()
/Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:22 +0x91
main.caller1()
/Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:15 +0x66
main.main()
/Users/haolin/GeekTime/Golang_Puzzlers/src/puzzlers/article19/q1/demo48.go:9 +0x66
exit status 2
(From panic to program crash)
Alright, up to this point, I believe you already have some understanding of the process of program termination after a panic is triggered. Understanding this process in depth and interpreting panic details correctly should be our essential skills, which are very important when debugging Go programs or troubleshooting errors for Go programs.
Summary #
In the recent two articles, we focused on the panic
function, recover
function, and defer
statement. Today, I mainly talked about the panic
function. This function is specifically used to trigger a panic. Panic can also be referred to as a runtime panic, which is a program exception that can only be thrown during program execution.
The Go runtime system may automatically throw a panic when a program encounters a severe error. We can also manually trigger a panic by calling the panic
function when needed. However, if not handled properly, panic will cause the program to crash and terminate the execution.
Thought Exercise #
How can a function convert a panic
into an error
type value and return it as the result value to the caller?
Click here to view the detailed code accompanying the article on the Go language column.