Golang: Technique for Handling Panic — Simply explained
What distinguishes a good programmer from a bad one is often down to handling of errors. This is no different in Go. Building resilient systems that will recover should be the goal for Go developers.
Panic vs Error in Golang
Go has a unique exception systems. In fact, it doesn’t believe in exception. Instead, it has the concept of error and panic, not exception. Error is generated by you as the Go coder, when you expect something to go wrong and when it did, you will return an error. Panic is when the error is unexpected and the program cannot continue on.
To recover from an error, you will simply write code to “handle” it.
valueReturned, err = doSomethingValuable() //function which returns
//a value and error
if err != nil {
fmt.Printf("Err encuntered %w", err)
}
Notice my mistake in saying you are recovering from an error. Why mistake? Error don’t crash the system, so actually there is nothing to “recover”.
Panic on the other hand needs to be recovered. If not recovered, as aforementioned in the first paragraph, the program will crash. In a long running and system critical program, crashing is the last behavior you would want. However, most Go user don’t learn early about handling panic.
Inducing a Panic
Before we handle panic “attacks”, let’s learn to induce a panic.
package handlepanicimport "errors"func AddingLargeNumbers() { var largeNumber int8
largeNumber = 127 largeNumber += 1 if largeNumber < 0 {
panic(errors.New("number is too large and has
wrapped."))//1
} print(largeNumber)
}
The panic function signature is as follows:
panic([]interface)
It accepts an interface as argument, which means that you can pass almost every “object” to panic. Even panic(nil) is par for the course. In //1 above, we choose to pass in an error. Why we do this will be obvious in a moment.
Recovering from panic
As aforementioned, when a panic occurs, the program will crash. Before that happens, the panic will go up the call stack and see if there is any recovery. So how exactly do we “recover”. In short, we call the “recover” function in a deferred function that is at a level that is at the callee level higher in stack of where the panic calls occur. Let’s continue with our example code to understand this:
package mainimport (
"fmt"
"handlepanic"
)func main() {
defer func() {
if err := recover(); err != nil { //1
fmt.Println("Gentle Recovery from panic err:
%w", err)}
cleanUp() //2
}() fmt.Println("Launching function...")
handlepanic.AddingLargeNumbers() //3
}func cleanUp() { fmt.Println("Continue and do cleanup")
}
In //3, we know that’s where the panic will occur. We write a defer closure function and call recover(). What’s returned from recover is the same “object” that is passed to panic, which in our case is an error. We then can handle the error in the same way that we are familiar (//1), including alerting the users of the program about what went wrong.
As aforementioned, once we recover from a panic, the program can continue to execute. This is exemplified in //2 where we take the opportunity to do some clean up. The output of the program will be:
$ go run main.go
Launching function...
Gentle Recovery from panic err: %w number is too large and has wrapped.
Continue and do cleanup
In an alternate universe, what if we don’t have code to “recover” from the panic. The output of the program would then be:
$ go run main.go
Launching function...
panic: number is too large and has wrapped.goroutine 1 [running]:
handlepanic.AddingLargeNumbers(...)
C:/Users/yaoji/Documents/Courses/Self Learning/Go/handlingPanic/handlePanic.go:13
main.main()
C:/Users/yaoji/Documents/Courses/Self Learning/Go/handlingPanic/example/main.go:17 +0x89
exit status 2
Notice that the code panicked and exit with non-zero code (meaning something anomalous had happened). This is bad if your code is meant to be long running. So after this article, I do hope you know and design in the handling of panic, in your code, from the get-go.
Happy Golang Coding!