Happy Holidays! - 20% off premium subscriptions when you use coupon code: HAPPYHOLIDAYS - View Pricing

Video:

Panic in Go

June 24, 2021

Course Instructor: Elliot Forbes

Hey Gophers! My name is Elliot and I'm the creator of TutorialEdge and I've been working with Go systems for roughly 5 years now.

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.

main.go
package main

func main() {
	panic("oh no")
}

Let’s see what happens when we run this:

$ go run main.go
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.

main.go
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:

$ go run main.go
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 to IPanicEasily 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:

main.go
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:

$ go run main.go
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.