Golang Panic and Recover Tutorial with Examples


Panic and Recover

In Go (Golang), a panic is a mechanism that allows you to halt the normal execution of a program when an unexpected or unrecoverable situation occurs. It is similar to an exception in other programming languages. When a panic is triggered, the program starts to unwind the call stack, running any deferred functions that are associated with each function call, and then crashes with a log message describing the cause of the panic.

The signature of the panic() function is as follows:

func panic(v interface{})

The panic() function takes a single argument v of the empty interface type (interface{}). This means that you can pass any value of any type as an argument to panic(). When a panic is triggered, the provided value is typically used to describe the reason for the panic, such as an error message or an error object.


Example of using panic() in Go

For example, let's say you are developing an application that processes sensitive information, and it requires a specific configuration file to work correctly. If the file is not found or is malformed, it's better to terminate the program immediately rather than continuing with incorrect or incomplete data.

Here's an example of using panic() without recover():

Example

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

type Config struct {
	SecretKey string `json:"secret_key"`
}

func main() {
	// Read configuration file.
	configData, err := ioutil.ReadFile("config.json")
	if err != nil {
		// If there's an error reading the file, panic and terminate the program.
		panic(fmt.Sprintf("Error reading configuration file: %v", err))
	}

	var config Config
	err = json.Unmarshal(configData, &config)
	if err != nil {
		// If there's an error unmarshaling the JSON data, panic and terminate the program.
		panic(fmt.Sprintf("Error parsing configuration data: %v", err))
	}

	if config.SecretKey == "" {
		// If the secret key is empty, panic and terminate the program.
		panic("Secret key is missing from configuration")
	}

	// Continue with the program execution.
	fmt.Println("Application started successfully.")
}

In this example, if the configuration file is missing, cannot be read, or the JSON data is malformed, the program will panic and terminate immediately. This ensures that the application doesn't continue with an invalid configuration, potentially causing data leaks or other issues.

However, this type of usage should be reserved for critical errors, and you should always strive to handle errors gracefully whenever possible.

panic: Error reading configuration file: open config.json: no such file or directory

goroutine 1 [running]:
main.main()
	/tmp/sandbox1728768757/prog.go:18 +0x179

Example of using recover() in Go

recover() is a built-in function in Go that is used to regain control of a panicking goroutine. When a panic() is called, the normal flow of the program is interrupted, and the deferred functions in the same goroutine are executed. You can use recover() within a deferred function to catch the panic value, handle the error, and prevent the program from crashing.

The recover() function, when called inside a deferred function, returns the value that was passed to panic(). If recover() is called outside a deferred function or if no panic() occurred, it returns nil.

The signature of the recover() function is as follows:

func recover() interface{}

The recover() function does not take any arguments, and it returns a value of type interface{}, which is the empty interface in Go. The empty interface can hold values of any type, so it allows recover() to return any value that was passed to the panic() function.

You can modify the given program to use recover() to handle the panic() gracefully. To do this, you can create a deferred function in the main() function that will call recover() and handle the panic scenario.

Example

package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
)

type Config struct {
	SecretKey string `json:"secret_key"`
}

func main() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Printf("An error occurred: %v\n", r)
			fmt.Println("Application terminated gracefully.")
		} else {
			fmt.Println("Application executed successfully.")
		}
	}()

	// Read configuration file.
	configData, err := ioutil.ReadFile("config.json")
	if err != nil {
		// If there's an error reading the file, panic and terminate the program.
		panic(fmt.Sprintf("Error reading configuration file: %v", err))
	}

	var config Config
	err = json.Unmarshal(configData, &config)
	if err != nil {
		// If there's an error unmarshaling the JSON data, panic and terminate the program.
		panic(fmt.Sprintf("Error parsing configuration data: %v", err))
	}

	if config.SecretKey == "" {
		// If the secret key is empty, panic and terminate the program.
		panic("Secret key is missing from configuration")
	}

	// Continue with the program execution.
	fmt.Println("Application started successfully.")
}

In this modified program, we have added a deferred function that will call recover(). If there's a panic, the deferred function will catch the panic value, print an error message, and gracefully terminate the application. If there is no panic, the program will continue execution, and the deferred function will print a success message when it finishes executing.

Running this code produces the following output:

An error occurred: Error reading configuration file: open config.json: no such file or directory
Application terminated gracefully.