Defer, Panic and Recover in Go
By: Uday Hiwarale
Tags: Golang Tutorial, Go Programming Language, Programming, Golang, Go Programming

In our previous tutorial, we discussed error handling in Go. In that tutorial, we learned that Go treats an error as a value. Such errors do not have the potential to break the programs, but they are designed to denote the anomaly in the business logic. For example, if a function expects values from 0-5 but if we supplied 10 then naturally the function should return a non nil error.

If you are used to another programming language like Java or JavaScript, then we know how to handle exception which is using try/catch block. A try block looks for an exception and if it finds any, it evaluates the catch block without disrupting the program.

Let’s say that based on some conditions, we want to exit from a program. Such conditions may occur when we know the caller of the program might not be able to properly consume the output or when the program may not run further properly based on the given conditions.

A panic is similar to an exception which can occur at runtime. A panic can be thrown by the runtime or can be deliberately thrown by the programmer to handle the worst case scenario. For example, accessing the out of bounds element from a slice (or an array) will throw a panic, as explained below.

Go provides built-in panic function to throw a panic. In the above example, the panic was thrown by the runtime at line no. 7. We can mimic the same behavior by checking the out of bounds (range) conditions and throwing the panic using panic function.

As you can see from the above example, we checked if the index is less than the length of a slice and thrown the panic manually when the condition is out of the bounds.

The panic function can receive any type of argument as its signature looks like below, and we know an empty interface type represents all the values.

panic(interface{})

When the panic occurs, the program is terminated by the runtime and an error message is shown (which is the input to panic function) as well as the stack trace printed till the point where the panic occurred.

But in reality, program execution is not terminated immediately. There is still some life left in the program and Go will finish some of the pending work before it terminates the program for good. Let’s see that in details.

In the above example, we have created a foo function which throws a panic. In a normal sequence of operations, main function gets executed and the first Println statement gets printed. Then foo function starts its execution and its first Println statement gets executed. Since the next statement is panic , Go stops the execution of foo function and second Println statement never gets called. Then control is returned to the caller which is foo() inside main function and program exits, printing panic error information to the console.

So, in nutshell, when a panic occurs, no further statements are executed and Go starts unwinding the stack by calling each pending statement in the stack until the last statement which was called from the main function.

So, is that all? The answer is no. We have learned about deferred functions in functions lesson. A deferred function is a function that will be executed just after the surrounding function hits returned statement or panics. A function can be deferred by adding defer keyword before the function call.

So why are we discussing the deferred functions? Because, when a panic occurs in the program, pending deferred functions are executed in LIFO order. Let’s see an example of this.

From the above example, we can guess what the execution sequence is. Inside the main function, defMain function is added to the deferred call stack of main function, then inside foo function, defFooStart function call is added to the deferred call stack of foo function. Since the next statement is panic , Go starts unwinding the call stack.

Since foo function panicked, Go executes defFooStart function as it is only deferred function from the foo call stack. Then control returns to the main function and since the program is still in panic mode, Go executes defMain deferred function. After that, the program is terminated as no statements after the panicked call (line no. 34) get executed.

You can have other deferred calls inside a deferred function which will follow the same mechanism of execution. We will see this later.

Recovering from the panic

But like try/catch way in other programming languages, do we have any way to recover from this panic? The answer is YES. You can use built-in recover function for that? But where? The answer is pretty obvious. Since the only functions which will get executed when the panic occurs are deferred function, we need to use recover function inside one of them.

recover() interface{}

The recover function returns the value passed to panic function and has no side effects. That means if our goroutine is not panicking, recover function will return nil . So checking the return value of recover() to nil is a good way to know if your program is packing, unless some idiot passes nil to the panic function which is a very rare case. So let’s see an example.

In the above example, we have called normMain function inside main which defers defFoo function call. Then program panics at line no. 21 and no statements after that will be executed. Since we have defFoo call pending as it was deferred, it gets executed next as it is the only function in the deferred call stack of normMain function.

Inside defFoo function, we have recover function call which will recover program from the panic. Then the control is returned to the function which called the panicking normMain function which main function and normal execution flow begin. Let’s see a little complex example to understand.

From the above example, we learned that when the Go program panics, Go doesn’t just start unwinding the deferred function stack all at once, that’s not how deferred functions work. If a panic occurs in a function, its deferred function will be called and if program recovers inside those deferred functions, normal execution flow begins after that function exits.

If a function call is deferred from another deferred function, you won’t be able to recover your program there. Because in order to recover a panicking program successfully, a deferred function with recover call must be executed right after the panic.

Below is an example of a program, where the recover function was called in deferred function which was called from another deferred function call whose caller panicked.

This obviously won’t work. Because the recover function does not know if the program panicked because from where it was called, everything was OK.

Below is another example of a panicking program where an attempt is made to recover the program from a deferred function which was called from a non-panicking function.

Here we will be able to recover the program but no statements after the function call statement, which panicked, will be executed.

Handling function return values

What happens to the return value when the function which supposed to return the value panics? Well, if the function doesn’t hit the return statement, it will return the default value of the return type. Let’s see an example.

We have reused the earlier example to demonstrate how we can recover from the panicking program. Inside accessElement function, we have deferred an immediately invoked function express (IIFE) to recover the program (because we know the program can panic there).

But at the moment, our deferred function is doing nothing except recovering the program. As we can see from the results, when the accessElement function panics, it returns 0 . So what can we do so that accessElement return a fallback value, let’s say the last element in the slice?

This is where we need to use named return values. Inside recover condition, we can override return value and function return that value.