Continue execution after recovered panic without knowing which function might panic

Viewed 53

I was referred to this question: Program recovered from panic does not exit as expected It works fine but it relies on knowing where the panic occurs in order to place the deferred function. My code is as follows.

package main

import "fmt"

func main() {
    defer recoverPanic()
    f1()
    f2()
    f3()
}

func f1() {
    fmt.Println("f1")
}

func f2() {
    defer f3() //<--- don't want to defer f3 here because I might not know f2 will panic, panic could occuer elsewhere
    fmt.Println("f2")
    panic("f2")
}

func f3() {
    fmt.Println("f3")
}

func recoverPanic() {
    if r := recover(); r != nil {
        fmt.Printf("Cause of panic ==>> %q\n", r)
    }
}

Having the deferred function call f3() in the panicking function works, output below.

f1
f2
f3
Cause of panic ==>> "f2"

What if you have an application where you don't know where a panic occurs, do I need to put a defer in every function that might panic? Commenting out the defer f3() gives me the following output.

f1
f2
Cause of panic ==>> "f2"

f3 never runs.

My question is how to continue execution of the program without having a deferred function call in every function that might panic?

1 Answers

You can't resume function execution after a panic. Panic is used when the current line of execution cannot continue correctly. Arbitrarily resuming execution after a panic (if it were possible) is begging immediately for another panic, because the state is already incorrect and just blazing ahead won't fix that.

For example, let's say a function panics when it tries to read out of bounds on a slice. How can it continue? What would it even mean to continue? Should it just read the out of bounds memory location and get garbage data? Continue with a zero value? Take a different value from the slice?

You must handle error cases; either by explicitly recovering, or preemptively checking / correcting conditions that will result in panic. At least in the standard library, functions that may spur a panic will say so in their documentation with an explanation of which conditions will result in panic.

If you commonly need to safely call void functions and recover from any panics, you can make a simple wrapper function for that.

func try(f func()) {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println("caught panic:", err)
        }
    }()
    f()
}

Then

func main() {
    try(f1)
    try(f2)
    try(f3)
}
Related