Function panic and recovery

Earlier in the chapter, it was stated that Go does not have the traditional exception mechanism offered by other languages. Nevertheless, in Go, there is a way to abruptly exit an executing function known as function panic. Conversely, when a program is panicking, Go provides a way of recovering and regaining control of the execution flow.

Function panic

During execution, a function may panic because of any one of following:

  • Explicitly calling the panic built-in function
  • Using a source code package that panics due to an abnormal state
  • Accessing a nil value or an out-of-bound array element
  • Concurrency deadlock

When a function panics, it aborts and executes its deferred calls. Then its caller panics, causing a chain reaction as illustrated in the following figure:

Function panic

The panic sequence continues all the way up the call stack until the main function is reached and the program exits (crashes). The following source code snippet shows a version of the anagram program that will cause an explicit panic if an output anagram file already exists when it tries to create one. This is done illustratively to cause the write function to panic when there is a file error:

package main 
... 
func write(fname string, anagrams map[string][]string) { 
   file, err := os.OpenFile( 
         fname,  
         os.O_WRONLY+os.O_CREATE+os.O_EXCL,  
         0644, 
   ) 
   if err != nil { 
         msg := fmt.Sprintf( 
               "Unable to create output file: %v", err, 
         ) 
         panic(msg) 
   } 
   ... 
} 
 
func main() { 
   words, err := load("dict.txt") 
   if err != nil { 
         fmt.Println("Unable to load file:", err) 
         os.Exit(1) 
   } 
   anagrams := mapWords(words) 
   write("out.txt", anagrams) 
} 

golang.fyi/ch05/anagram2.go

In the preceding snippet, the write function calls the panic function if os.OpenFile() method errors out. When the program calls the main function, if there is an output file already in the working directory, the program will panic and crash as shown in the following stack trace, indicating the sequence of calls that caused the crash:

> go run anagram2.go

panic: Unable to create output file: open out.txt: file exists
goroutine 1 [running]:
main.write(0x4e7b30, 0x7, 0xc2080382a0)
/Go/src/github.com/vladimirvivien/learning-go/ch05/anagram2.go:72 +0x1a3

main.main()
Go/src/github.com/vladimirvivien/learning-go/ch05/anagram2.go:103 +0x1e9
exit status 2

Function panic recovery

When a function panics, as explained earlier, it can crash an entire program. That may be the desired outcome depending on your requirements. It is possible, however, to regain control after a panic sequence has started. To do this, Go offers the built-in function called recover.

Recover works in tandem with panic. A call to function recover returns the value that was passed as an argument to panic. The following code shows how to recover from the panic call that was introduced in the previous example. In this version, the write function is moved inside makeAnagram() for clarity. When the write function is invoked from makeAnagram() and fails to open a file, it will panic. However, additional code is now added to recover:

package main 
... 
func write(fname string, anagrams map[string][]string) { 
   file, err := os.OpenFile( 
         fname,  
         os.O_WRONLY+os.O_CREATE+os.O_EXCL,  
         0644, 
   ) 
   if err != nil { 
         msg := fmt.Sprintf( 
               "Unable to create output file: %v", err, 
         ) 
         panic(msg) 
   } 
   ... 
} 
 
func makeAnagrams(words []string, fname string) { 
   defer func() { 
         if r := recover(); r != nil { 
               fmt.Println("Failed to make anagram:", r) 
         } 
   }() 
 
   anagrams := mapWords(words) 
   write(fname, anagrams) 
} 
func main() { 
   words, err := load("") 
   if err != nil { 
         fmt.Println("Unable to load file:", err) 
         os.Exit(1) 
   } 
   makeAnagrams(words, "") 
} 

golang.fyi/ch05/anagram3.go

To be able to recover from an unwinding panic sequence, the code must make a deferred call to the recover function. In the previous code, this is done in the makeAnagrams function by wrapping recover() inside an anonymous function literal, as highlighted in the following snippet:

defer func() { 
   if r := recover(); r != nil { 
         fmt.Println("Failed to make anagram:", r) 
   } 
}() 

When the deferred recover function is executed, the program has an opportunity to regain control and prevent the panic from crashing the running program. If recover() returns nil, it means there is no current panic unwinding up the call stack or the panic was already handled downstream.

So, now when the program is executed, instead of crashing with a stack trace, the program recovers and gracefully displays the issue as shown in the following output:

> go run anagram3.go
Failed to make anagram: Unable to open output file for creation: open out.txt: file exists

Note

You may be wondering why we are using a nil to test the value returned by the recover function when a string was passed inside the call to panic. This is because both panic and recover take an empty interface type. As you will learn, the empty interface type is a generic type with the ability to represent any type in Go's type system. We will learn more about the empty interface in Chapter 7, Methods, Interfaces and Objects during discussions about interfaces.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.154.64