Recover is a built-in function that regains control of a panicking goroutine. If the current goroutine is panicking, a call to recover will capture the value given to panic and resume normal execution.

func f() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
    fmt.Println("Calling g.")
    fmt.Println("Returned normally from g.")

Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect.


Firstly, we need to investigate what happens when we call recover(). When using recover(), the compiler will compile this statement into runtime.gorecover(). Let’s see the source code of this function:

func gorecover(argp uintptr) any {
	// Must be in a function running as part of a deferred call during the panic.
	// Must be called from the topmost function of the call
	// (the function used in the defer statement).
	// p.argp is the argument pointer of that topmost deferred function call.
	// Compare against argp reported by caller.
	// If they match, the caller is the one who can recover.
	gp := getg()
	p := gp._panic
	if p != nil && !p.goexit && !p.recovered && argp == uintptr(p.argp) {
		p.recovered = true
		return p.arg
	return nil

Let’s analyze:
The program takes the pointer to the current goroutine via getg(). Pay attention to the attribute _panic. One of the conditions for recover() to be executed is that the _panic must be not nil. In the same way, _panic of current gorountine must be not nil.

func gopanic(e interface{}) {
    gp := getg()
    var p _panic = gp._panic
    gp._panic = (*_panic)(noescape(unsafe.Pointer(&p))) 

We can see _panic is manipulated inside runtime.gopanic(), this is the function that is executed when we call panic(). So, if the current goroutine does not panic, its _panic is always nil. Then no matter how many times we call recover(), it will always return nil and do nothing.


Categorized in:

Tagged in: