An in depth understanding of golang's exception handling mechanism

  • 2020-06-03 06:53:57
  • OfStack

preface

It is well known that in many object-oriented languages such as java or php, exception handling relies on throw and catch. In THE go language, the panic and recover functions match the throw and catch statements respectively at the action level, but there are also differences. Without further ado, let's start with a detailed introduction.

From a design perspective, the panic and recover functions are suitable for those real exceptions (such as integer division by 0), while the throw catch finally mechanism is often used to handle custom exceptions at the business level. Therefore, panic and recover should be used with caution in go.

In the use of the above two exception mechanisms, the direction of the control flow is similar when handling exceptions.

Examples are given below:

try catch finally mechanism


 try{
 throw new Exception();
 } catch(Exception $e) {
 do something ...
 } finally {
 
 }

In this mechanism, we place the statement that may throw an exception or the statement that throws a custom exception into the try block, and in the catch block, we catch the exception thrown by the above statement and handle the different exceptions with alarm or log, etc. After that, the control flow goes into the finally statement block. Without the finally statement, the control flow goes into the statement after catch. That is, in this mechanism, the control flow is transferred to the statement after exception capture in the same level.

panic recover defer mechanism

In the exception mechanism of go, panic can interrupt the original control flow into a "panic" process. This panic process can be generated by explicitly calling the panic() function or by runtime errors (such as accessing out-of-bounds array indices). panic will be thrown to this layer and all its upper layers step by step in the function calling it. If no recover captures it, crash will be generated after the program exits. If captured by recover in a layer of defer statements, the control flow goes into the statement after recover.


 /* example 1 */
 package main
 import (
 "fmt"
 )

 func f() {
 defer func() {
  fmt.Println("b")
  if err := recover();err != nil {
  fmt.Println(err)
  }
  fmt.Println("d")
 }()
 fmt.Println("a")
 panic("a bug occur")
 fmt.Println("c")
 }

 func main() {
 f()
 fmt.Println("x")
 }

In the above example, the output result is:


 a
 b
 a bug occur
 d
 x

This indicates that panic thrown in f is captured by recover in its own defer statement, and then the control flow enters into the statement after recover, that is, printing d and x, after which the process exits normally.


 /* example 2 */
 package main
 import (
  "fmt"
 )

 func g() {
  defer func() {
   fmt.Println("b")
   if err := recover();err != nil {
    fmt.Println(err)
   }
   fmt.Println("d")
  }()
  f()
  fmt.Println("e")
 }

 func f() {
  fmt.Println("a")
  panic("a bug occur")
  fmt.Println("c")
 }

 func main() {
  g()
  fmt.Println("x")
 }

The output of the above cases is:


 a
 b
 a bug occur
 d
 x

The process goes through such a process: f() panic is thrown into g() because it does not define the defer statement itself. g() recover is defined in the defer statement. After panic is captured and the remaining defer statements are executed, the control flow is transferred to main() Function until the process ends.

conclusion


Related articles: