DEV Community

Cover image for Recover in Go. Panic and Recover in Golang
Vasyl Pominchuk
Vasyl Pominchuk

Posted on

Recover in Go. Panic and Recover in Golang

Table of contents

  1. Difference Between throw/catch in Other Languages
  2. Understanding defer() Function
  3. Understanding panic() Function
  4. How to recover()

Difference Between throw/catch in Other Languages

Before we dive into Golang's error recovery mechanisms, let's briefly touch on the more traditional "throw" and "catch" paradigm found in languages like PHP, Java, JavaScript and so on. In those languages, an exception (or error) is thrown when a runtime error occurs, and it propagates up the call stack until it's caught by an appropriate try/catch block.

In contrast to languages like PHP, Java and JavaScript, Golang adopts a unique approach to error handling by emphasizing explicit error handling through return values. In Golang, functions typically return both a result and an error, empowering developers to explicitly check for and manage errors at each step. This approach provides greater control over the program's flow, diverging from the traditional "throw" and "catch" paradigm found in other languages.

Golang introduces panic, defer, and recover as alternatives to the throw and catch concepts. While these mechanisms share a common ground, they differ in important details, offering a more explicit and controlled approach to error handling in Golang.

Understanding defer() Function

One of Golang's distinctive features is the defer() function, which allows you to schedule a function call to be executed after the surrounding function returns. This can be particularly useful for tasks like closing files, releasing resources, or, as we'll see, handling panics.

When multiple defer statements are used within a function, they are executed in reverse order, i.e., the last defer statement is executed first, followed by the second-to-last, and so on. The execution order is essentially a Last In, First Out (LIFO) stack.

Here's an example to illustrate the execution order of multiple defer statements:

package main

import "fmt"

func exampleFunction() {
    defer fmt.Println("Deferred statement 1")
    defer fmt.Println("Deferred statement 2")
    defer fmt.Println("Deferred statement 3")

    fmt.Println("Regular statement 1")
    fmt.Println("Regular statement 2")
}
Enter fullscreen mode Exit fullscreen mode
Regular statement 1
Regular statement 2
Deferred statement 3
Deferred statement 2
Deferred statement 1
Enter fullscreen mode Exit fullscreen mode

As you can see, the deferred statements are executed in reverse order after the regular statements. This behavior is useful in scenarios where you want to ensure certain cleanup tasks or resource releases are performed, regardless of the flow of execution within the function.

Understanding panic() Function

In Golang, the panic() function is used to trigger a runtime panic. When a panic occurs, the normal flow of the program is halted, and the program starts unwinding the call stack, running any deferred functions along the way.

func examplePanic() {
    // some logic
    if somethingBad {
        panic("Something bad happened!") // panic is triggered
    }
    // rest of the function logic
}
Enter fullscreen mode Exit fullscreen mode

While panics might seem disruptive, they serve a crucial purpose in signaling unrecoverable errors or exceptional conditions. When a panic() is triggered, it enters a panicking mode, initiating the execution of deferred functions and unwinding the call stack.

Panics can be initiated either directly by calling panic() with a single value as an argument, or they can be caused by runtime errors.
One common runtime error in Golang is attempting to access an index that is out of bounds in a slice. Here's an example that causes a runtime error:

package main

import "fmt"

func main() {
    // Creating a slice with three elements
    numbers := []int{1, 2, 3}

    // Accessing an index that is out of bounds
    outOfBoundsIndex := 5
    value := numbers[outOfBoundsIndex] // This line will cause a runtime error

    // This line won't be reached due to the runtime error
    fmt.Println("Value at index", outOfBoundsIndex, "is:", value)
}
Enter fullscreen mode Exit fullscreen mode

In this example, the numbers slice has three elements, but we attempt to access the element at index 5, which is beyond the length of the slice. When the program is run, it will result in a runtime error similar to:

panic: runtime error: index out of range [5] with length 3
Enter fullscreen mode Exit fullscreen mode

This panic occurs at runtime, and if not handled, it would terminate the program. Handling such runtime errors is crucial in real-world applications.

How to recover()

To gracefully handle panics and potentially recover from them, Golang provides the recover() function. When used in conjunction with defer, recover() allows you to capture the panic value and resume normal execution. If we are in panicking mode, the call to recover() returns the value passed to the panic function.

func main() {
    defer func() {
        if r := recover(); r != nil {
            // handle the panic
            fmt.Println("Recovered from panic:", r)
        }
    }()
    // rest of the function logic that might panic
}
Enter fullscreen mode Exit fullscreen mode

In the example above, the recover() function is used to capture the panic value, which is then printed as part of the recovery process. It's important to note that recover() only works when called directly from a deferred function. If there is no panic or if recover() is called outside the context of deferred functions during panicking, it returns nil.

In real world applications you will face with problems of converting panic to normal error, this is the same that try/catch do for us.

package main

import (
    "fmt"
)

func exampleRecoverToError() (err error) {
    defer func() {
        if r := recover(); r != nil {
            // convert panic to an error
            err = fmt.Errorf("panic occurred: %v", r)
        }
    }()

    // rest of the function logic that might panic
    panic("Something went wrong!")

    // This line won't be reached due to the panic
    return nil
}

func main() {
    if err := exampleRecoverToError(); err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("No error.")
    }
}
Enter fullscreen mode Exit fullscreen mode

In the example above we recover our application from panicing mode and convert from panic to normal error using named return technic.

Understanding that the call to recover() returns the value passed to the panic function allows for more nuanced and context-aware recovery mechanisms, enabling you to inspect and respond to specific panic conditions within your Golang application.

Kindly visit my personal website for additional intriguing content.

Conclusion

Understanding and mastering error recovery in Golang is crucial for writing robust and reliable applications. The combination of defer(), panic(), and recover() provides a powerful mechanism for handling errors gracefully and ensuring your applications remain resilient even in the face of unexpected issues. By employing these techniques, you can elevate your error-handling game and build more robust Golang applications.

Top comments (0)