Defer, Panic and Recover in Go
As we have seen the earlier tutorial, Go treats error as a value. But at times, Go breaks the program in runtime and such types of errors are called as Runtime Panics. A panic can be triggered manually and we can recover from this panic using built-in capabilities provided by Go.
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
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
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.
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.
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
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
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
defMain function is added to the deferred call stack of
main function, then inside
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.
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 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
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
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.
I know that this tutorial might be a little difficult to comprehend if you are not paying attention but in a nutshell, panic and recover is a very small functionality and their application is very limited. You can follow this YouTube video to understand how panic and recover works.
Sometimes you are not sure if a program will panic but when you do, you must set a mechanism to recover it. For example, for out of bound conditions or file system access. If you are developing a package for people to use, and you are sure that a program cannot be allowed to function in some abnormal situations, you should use
panic to terminate the program. This could be useful to protect the machine on which this program is running.