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.

Uday Hiwarale
Jun 17 · 8 min read
(source: unsplash.com)

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.

https://play.golang.org/p/lM9hggLnCM2

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.

https://play.golang.org/p/Q7R8cU1OYuj

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.

https://play.golang.org/p/kDcZ4-CkC8A

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.

https://play.golang.org/p/uSXAmj61ADt

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.

https://play.golang.org/p/lnOiERNgRwX

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.

https://play.golang.org/p/8du5HAiSRta

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.

https://play.golang.org/p/zAlYEyHp5Kp

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.

https://play.golang.org/p/NGjWyrSHcZj

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.

https://play.golang.org/p/B_HGZOTi2nJ

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.

https://play.golang.org/p/sc8b82gysaJ

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.


RunGo

A place to find introductory Go Programming Language tutorials and learning resources. Like my other tutorials on Web Development, Run Go publication features important Go articles with deep dive into core of the language with examples and sample code.

Uday Hiwarale

Written by

IIT • Software Developer • India | Follow: github.com/thatisuday | Ask: thatisuday@gmail.com | World iza better place coz some still write on Medium for free :)

RunGo

RunGo

A place to find introductory Go Programming Language tutorials and learning resources. Like my other tutorials on Web Development, Run Go publication features important Go articles with deep dive into core of the language with examples and sample code.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade