21 the Panic Function, the Recover Function and the Defer Statement Part 1

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.