In this post, we will look at Golang Panic and Recover mechanism in detail.

If you are completely new to Golang, I will suggest you to start with Golang Hello World to understand the basics.

A typical program can have two types of errors. The first ones are those anticipated by the programmers. We can handle them using the Golang error handling using the error interface. The second type of errors are those that programmers may not have anticipated. In other words, they are unforeseen errors.

A panic is an unforeseen error. It can lead to abnormal termination of the program. Panics are a usually a result of common programming mistakes that might creep in while development. However, the important point is not why panics occur in a program but how we can handle them before they can terminate our program unexpectedly.

1 – What are Panics in Golang?

A panic is an unforeseen circumstance occurring in a program.

There are some operations that automatically return a panic and stop the program from further execution. For example, accessing accessing an array index that is out-of-bounds, calling methods on nil pointer and so on. As you can see, these are mostly a result of programming mistake. Therefore, a compiler cannot detect them during compilation step.

Let’s look at a panic to understand better.

package main

import "fmt"

func main() {
	books := []string{
		"The Eye of the World",
		"The Great Hunt",
		"The Dragon Reborn",
	}

	fmt.Println("The last book in the Books Array is: ", books[len(books)])
}

In the above program, we declare a books array. It has the first 3 books from the Wheel of Time series. We try to print the last book in the array. However, we the index as length of the array. In this case, the length would be 3. However, since arrays are zero-based, the last index is actually 2. This causes an array index out-of-bounds situation. As a result, the program has no option but to terminate since it cannot do something that is impossible.

If we run the above program, we will get the below output.

panic: runtime error: index out of range [3] with length 3

goroutine 1 [running]:
main.main()
	/Users/saurabhdashora/GoProjects/go-demo-program/demo.go:12 +0x1b

We will walkthrough the error message in the next section.

3 – Dissecting the Golang Panic Message

A panic contains two important points. First is a message containing the cause of the panic. Second is a stack-trace that helps us identify where the panic occurred. Together, these pieces of information can help a developer find the error and fix it.

The message part of the panic starts with the panic: prefix. See below:

panic: runtime error: index out of range [3] with length 3

Next, we get the information that it was a runtime error. Lastly, we get the reason for the error. In this case, the reason is trying to access an array index that is out-of-bounds.

After the message, we have the actual stack trace of the error. Basically, the stack trace forms a map that we can follow to locate the exact line where the error occurred.

goroutine 1 [running]:
main.main()
	/Users/saurabhdashora/GoProjects/go-demo-program/demo.go:12 +0x1b

The above trace shows that the panic was generated at line 12 in our program. It also tells that the panic was generated in the main() function from the main package.

The goroutine statement tells the ID number of the goroutine along with its state when the panic occurred. In this case the state was running.

4 – The Built-In Panic Function

We can also generate panics in our program by calling the built-in panic() function. See below example:

package main

func main() {
	panicking()
}

func panicking() {
	panic("The program is panicking")
}

The panic() function simply takes a single argument in the form of a string message. Using panics can signal to the clients of our function that they have done something wrong. However, the best practice is to handle errors using the standard error interface.

The output of running this program is as follows:

panic: The program is panicking

goroutine 1 [running]:
main.panicking(...)
	/Users/saurabhdashora/GoProjects/go-demo-program/demo.go:8
main.main()
	/Users/saurabhdashora/GoProjects/go-demo-program/demo.go:4 +0x27

Notice the stack trace in this case. It tells us the entire chain of calls that led to the panic. This can help a developer trace the execution flow that led to the eventual panic.

5 – Deferred Functions

Often the programs are holding resources that need to be cleaned up properly when a panic occurs.

Golang allows us to defer the execution of a function call until the function calling it has completed execution. Deferred functions run even when a panic occurs. In other words, we can use them as safety mechanism in case a panic occurs in our program.

See below example:

package main

import "fmt"

func main() {
	defer deferring()
	fmt.Println("Before Panic")
	panic("Creating Panic")
}

func deferring() {
	fmt.Println("The deferred function executed successfully")
}

Here, we create a function deferring that prints a message. A function can be deferred by calling it with the defer keyword as in defer deferring().

If we run this program, we will see the below output:

Before Panic
The deferred function executed successfully
panic: Creating Panic

goroutine 1 [running]:
main.main()
	/Users/saurabhdashora/GoProjects/go-demo-program/demo.go:8 +0x8b

As you can see, the first statement is Before Panic. This is because the deferring() function will not execute. Then, we created a panic using the panic() function. However, before the panic could terminate the program, the deferring() function executes. At the end, we have the panic stack trace and message.

6 – Golang Handling Panic with Recover

Panics have only one recovery mechanism and that is by using the recover() function. Basically, this function allows us to intercept a panic on its way up the call stack.

The recover() function is also a built-in function and hence, we can simply call it without any special imports.

See below example of the recover() function in action.

package main

import "fmt"

func main() {
	printBook()
	fmt.Println("We handled the panic")
}

func printBook() {
	defer deferring()
	books := []string{
		"The Eye of the World",
		"The Great Hunt",
		"The Dragon Reborn",
	}

	fmt.Println("The last book in the Books Array is: ", books[len(books)])
}

func deferring() {
	if err := recover(); err != nil {
		fmt.Println("An error occurred:", err)
	}
}

We again take the example of our array index out-of-bounds. If we wanted to handle the panic caused earlier, we will need to use the recover() function.

Basically, we use the deferring() function in the printBook() function. However, we defer its execution. Within the deferring() function, we first call the recover() function and assign the error it returns to a variable. If the err is not nil (in the case of a panic), we print a normal message.

The output of the above program is as follows:

An error occurred: runtime error: index out of range [3] with length 3
We handled the panic

As you can see, despite the panic, the program did not terminate and the messages were displayed.

Conclusion

With this, we have successfully understood Golang Panic and Recover mechanism with various examples. We also saw how to defer a function execution and use it to handle panics gracefully.

If you have any comments or queries, please feel free to mention in the comments section below.


Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *