Panic! In the Go program. Not quite as “catchy” as the band name, but it is however, something you should know about if you are a Go developer!
In this video, we are going to cover all things Panic and why/when to use them, and more importantly, when not to use them!
Panic
Now, panic
is what is known as a built-in function within Go that can be called anywhere, without the need to import an additional package. It’s similar to the println
functions that we’ve used in earlier videos in the course in that regard.
package main
func main() {
panic("oh no")
}
Let’s see what happens when we run this:
panic: oh no
goroutine 1 [running]:
main.main()
/Users/elliot/Documents/Projects/TutorialEdge/courses/beginners-guide-to-go/21-panic-in-go/main.go:4 +0x39
exit status 2
As you can see from the output, we’ve printed out the stack trace which leads us back up to where in our Go application a panic has occured.
Deferred Functions
An important point to note is that when a function panics, all the deferred functions for that particular function are going to be executed as they normally would.
This means that if for some reason your application panics and it has open database connections etc, the deferred functions that handle cleaning up these connections will still be called.
Let’s take a look at this in action with a small Go application. Let’s create 2 additional functions IPanicEasily
and MyAwesomeFunction
and within these let’s add some deferred functions in there that will be executed as each function finishes.
package main
import (
"fmt"
)
func IPanicEasily() {
defer func() {
fmt.Println("1")
}()
panic("I panic easily")
}
func MyAwesomeFunction() (err error) {
defer func() {
fmt.Println("2")
}()
IPanicEasily()
return nil
}
func main() {
defer func() {
fmt.Println("3")
}()
fmt.Println("Panic! In the Go App")
MyAwesomeFunction()
fmt.Println("finished main function successfully")
}
Cool, so at this point we have a chain of functions within our application that all have deferred functions within them.
Our main
function calls MyAwesomeFunction
which in turn calls IPanicEasily
. When IPanicEasily
panics, we will see the deferred function being executed before we then traverse up to MyAwesomeFunction
and look for any deferred functions to execute. We’ll then find and execute that deferred function before traversing further up the chain to our main
function.
Once again, we’ll then search for any deferred functions and execute them before terminating our application.
Let’s see this in action by running this now:
Panic! In the Go App
1
2
3
panic: I panic easily
goroutine 1 [running]:
main.IPanicEasily()
/Users/elliot/Documents/Projects/TutorialEdge/courses/beginners-guide-to-go/21-panic-in-go/main.go:11 +0x5b
main.MyAwesomeFunction(0x0, 0x0)
/Users/elliot/Documents/Projects/TutorialEdge/courses/beginners-guide-to-go/21-panic-in-go/main.go:18 +0x4c
main.main()
/Users/elliot/Documents/Projects/TutorialEdge/courses/beginners-guide-to-go/21-panic-in-go/main.go:27 +0xa5
exit status 2
As you can see, the print statements 1, 2, and 3 are all printed in the order in which the deferred functions are executed.
Note - if we add additional code to
MyAwesomeFunction
underneath our call toIPanicEasily
it won’t be executed as it stands. It’s only going to execute the deferred function before carrying on up the chain.
Recovering from Panics
Panics provide you a way to fail quickly within your application and they cascade up to the main function ensuring to execute the deferred functions in each of the functions within the chain.
We can take advantage of this behaviour of a panicking function always executing deferred functions normally by using the recover
built-in function. This function effectively allows us to prevent a panic from cascading further up the chain and terminating our application earlier than we’d normally like.
Let’s update our MyAwesomeFunction
to incorporate this recover
built-in function in its deferred function and return an appropriate error.
Now in the video I break this down into two steps, however for brevity here I’m going to keep it as one step. We can start off by updating the function signature to return (err error)
. We can then check to see if the value we receive from the built-in recover
function is not nil, if it contains a value, we know we’ve recovered from a panic. Within the body of this check, we can then set the err
to be equal to a new error using the errors
package:
package main
import (
"errors"
"fmt"
)
func IPanicEasily() {
panic("I panic easily")
}
func MyAwesomeFunction() (err error) {
defer func() {
if r := recover(); r != nil {
fmt.Println("recovered from a small panic")
err = errors.New("i panic easily panicked")
}
fmt.Println("2")
}()
IPanicEasily()
return nil
}
func main() {
fmt.Println("Panic! In the Go App")
if err := MyAwesomeFunction(); err != nil {
fmt.Println("my awesome function returned an error")
fmt.Println(err.Error())
}
fmt.Println("finished main function successfully")
}
I’ve also updated the main
function to catch any errors from the call to MyAwesomeFunction
and print these out. With these updates in place, let’s try and run this now:
Panic! In the Go App
recovered from a small panic
2
my awesome function returned an error
i panic easily panicked
finished main function successfully
Best Practices
Before you sprinkle panic
into your applications, there are a few things you should know about.
Firstly, panic
shouldn’t generally be used within applications and or libraries. It’s better to return errors that downstream callers of functions can then handle appropriately.
Secondly, I would avoid defaulting to having a recover deferred function unless it is absolutely necessary. Handling errors gracefully and in a more verbose manner is usually a far better option for you applications.
Conclusion
Cool, so let’s wrap up what we’ve covered in this video. We began by covering what the panic function is and how you can use it, then we looked at some of the best practices are around when and where you should call this panic function.