Panics, stack traces and how to recover [best practice]

yourbasic.org/golang

A panic is an exception in Go

Panics are similar to C++ and Java exceptions, but are only intended for run-time errors, such as following a nil pointer or attempting to index an array out of bounds. To signify events such as end-of-file, Go programs use the built-in error type. See Error handling best practice and 3 simple ways to create an error for more on errors.

A panic stops the normal execution of a goroutine:

A panic is caused either by a runtime error, or an explicit call to the built-in panic function.

Stack traces

A stack trace – a report of all active stack frames – is typically printed to the console when a panic occurs. Stack traces can be very useful for debugging:

Interpret a stack trace

Here’s an example of a stack trace:

goroutine 11 [running]:
testing.tRunner.func1(0xc420092690)
	/usr/local/go/src/testing/testing.go:711 +0x2d2
panic(0x53f820, 0x594da0)
	/usr/local/go/src/runtime/panic.go:491 +0x283
github.com/yourbasic/bit.(*Set).Max(0xc42000a940, 0x0)
	../src/github.com/bit/set_math_bits.go:137 +0x89
github.com/yourbasic/bit.TestMax(0xc420092690)
	../src/github.com/bit/set_test.go:165 +0x337
testing.tRunner(0xc420092690, 0x57f5e8)
	/usr/local/go/src/testing/testing.go:746 +0xd0
created by testing.(*T).Run
	/usr/local/go/src/testing/testing.go:789 +0x2de

It can be read from the bottom up:

The indented lines show the source file and line number at which the function was called. The hexadecimal numbers refer to parameter values, including values of pointers and internal data structures. Stack Traces in Go has more details.

To print the stack trace for the current goroutine, use debug.PrintStack from package runtime/debug.

You can also examine the current stack trace programmatically by calling runtime.Stack.

Level of detail

The GOTRACEBACK variable controls the amount of output generated when a Go program fails.

Recover and catch a panic

The built-in recover function can be used to regain control of a panicking goroutine and resume normal execution.

Because the only code that runs while unwinding is inside deferred functions, recover is only useful inside such functions.

Panic handler example

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() int {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
	}()
	m := 1
	panic("foo: fail")
	m = 2
	return m
}
foo: fail
main received 0

Since the panic occurred before foo returned a value, n still has its initial zero value.

Return a value

To return a value during a panic, you must use a named return value.

func main() {
	n := foo()
	fmt.Println("main received", n)
}

func foo() (m int) {
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
			m = 2
		}
	}()
	m = 1
	panic("foo: fail")
	m = 3
	return m
}
foo: fail
main received 2

Test a panic (utility function)

In this example, we use reflection to check if a list of interface variables have types corre­sponding to the para­meters of a given function. If so, we call the function with those para­meters to check if there is a panic.

// Panics tells if function f panics with parameters p.
func Panics(f interface{}, p ...interface{}) bool {
	fv := reflect.ValueOf(f)
	ft := reflect.TypeOf(f)
	if ft.NumIn() != len(p) {
		panic("wrong argument count")
	}
	pv := make([]reflect.Value, len(p))
	for i, v := range p {
		if reflect.TypeOf(v) != ft.In(i) {
			panic("wrong argument type")
		}
		pv[i] = reflect.ValueOf(v)
	}
	return call(fv, pv)
}

func call(fv reflect.Value, pv []reflect.Value) (b bool) {
	defer func() {
		if err := recover(); err != nil {
			b = true
		}
	}()
	fv.Call(pv)
	return
}

Share this page: