5 common Golang panics
that ninjas should know

Don’t panic, recover() will take care of you.

golang ninja
Golang is riding the crest of the wave. Many recent and interesting projects are developed in Go: Docker, Vault, Kubernetes, Prometheus … Golang wants to be portable, efficient, multi-cores oriented, fun and cool according to the Go project page. If you’re new to it though, its syntax could be disturbing.

Go has no try/catch mechanism. The reason for this is simple: there is no exception. Golang comes with error objects instead, and developers are required to deal with them when found in the return of a function. This is a really interesting approach as it forces developers to handle unexpected behaviors.

Even with the best code, sometimes things still go wrong and the application finally crashes, leaving you with… this boring/annoying panic.

Today, I want to share with you my recent experience about panics. This post is designed to get you started and avoid the most common traps. Three topics are covered:

  • How Defer, Panic and Recover work
  • 5 common Golang panic issues
  • How to trigger your own panic

“recover()” will save you from an incoming tragedy

Before introducing what a “recover” function is, you must understand what is a “panic”. A panic is a runtime error that is both unexpected and not managed in your code. For instance, a panic can occur when you try to divide something by zero or try accessing the 14th item of an array which size is 3. If the panic is not caught with recover(), it causes your app to crash.

So “Golang Recover” is a built-in function and the way to catch exceptions. When “recover()” is called, it checks if a panic has occurred somewhere in the execution stack. And if this is the case, the recover stops the failing process and allows developers to handle the error in order to go back to a normal state.

The last thing to be aware of is the instruction “defer” as the recover function is closely linked with this instruction. Let’s look at these 3 code examples to understand the defer and how the execution stack in Golang works.

Which one of the snippets is not going to fail?

#### Define 3 methods: the first just prints something, the second crashes the app, the last deals with a panic.

# method 1

func say(text string) {
      fmt.Println(text)
}

# method 2

func firePanic() {
      i := 42 / 0 // A panic will occur right here
      fmt.Printf("This will never be reached (%s)", i)
}

# method 3

func rescue() {
      r := recover() // We stopped here the failing process!!!
      if r != nil {
             fmt.Println("Panic has been recover")
      }
}

#### Snippet 1

func main() {

      // An amazing app starts here
      say("hello world")
      firePanic()
      say("never reached, if panics are managed")

// At the end of main, call rescue function
 	rescue()

}

# Snippet 2

func main() {

      // An amazing app starts here
      say("hello world")
      firePanic()
      say("never reached, if panics are managed")

// At the end of main, call rescue function
      defer rescue()

}

# Snippet 3

func main() {

      // At the end of main, call rescue function
      defer rescue()

      // An amazing app starts here
      say("hello world")
      firePanic()
      say("never reached, if panics are managed")
}

Did you find it?

All three are different. But only the 3rd will end normally. Let’s see why by looking at the execution stack:

In the first snippet, the execution stack is simple. All functions (say, firePanic, rescue) are wrapped in the main.

If the code fails somewhere in, the app simply stops in error (nothing is wrapped by the main function). The rescue function is never reached here because a panic has occurred in the firePanic function and before the rescue one.
golang panic

Illustration of the pseudo execution stack for snippet 1

For snippets 2 and 3, a defer is used and behavior is thus different.

When the defer instruction is reached, Golang adds a step/layer to the execution stack: the code prefixed by the defer instruction is postponed. And right after the main function is terminated , Go executes the postponed code/function deferred, which is really useful for closing streams, files and others resources or … catching panics.

Defer does more than just putting the code at the end of your function. The postponed code will always execute, even if a panic or something occurred before.

To remember what the defer instruction does, keep in mind: “whatever happened, always execute [this code] at the end”.

Binding a recover with a defer lets you manage any panic that could occur.

golang panic

Illustration of the pseudo execution stack for snippet 3

Keep in mind something really important with the defer instruction: it postpones actions at the runtime, not at the compilation one. So, if your code failed before reaching the defer instruction nothing will be added to the execution stack.

That is why snippet 2 still fails. The rescue function is never deferred.
golang panic

Illustration of the pseudo execution stack for snippet 2

The last thing to know about defer is how it postpones each action. Defer is working as a stack: FILO (First-in Last-out). So the first defer instruction will be executed the last.

Look at this example below, where all instructions are deferred.

golang panic

Another example where the code fails

The code produces a panic error because the rescue function is executed at the beginning.

At this point, we know how to use the defer instruction. Let’s now see how to stop the failure chain and catch panics.

The “recover” function is more something of a “has a panic occurred?” than what I’ve previously written. The Golang “Recover” is a built-in function that regains control of a panicking goroutine. So it’s only useful inside a deferred code block or a function. During normal execution, i.e. no panic has occurred, a call to this function would return nil. There is no side effect. If the current application is panicking, recover will capture the error given to panic and resume the goroutine (the application) to a normal execution.

The code below allows anyone to check the state of the execution and code the proper action to do:

# Snippet 4

func rescue() {

      // Check the execution state
r := recover()

// nil means we are in a normal execution
if r != nil {
	// here, a panic has been occurred somewhere 
      fmt.Printf("Panic (code %d) has been recover from somewhere\n", r)

	// do something here to handle the panic
	...
}

}

You finally know all the basics to properly manage panics.

Now the only difficulty is a matter of how deep and multithreading your code is and where panics occurred. I recommend these 2 articles to go further:

Now, let’s meet the most common panic issues.

5 usual suspects

Disclaimer: this part targets people starting with Go. If you already feel confident with Golang panics, just skip this section and move on to the next one.

There are many ways to get a panic. But fortunately, they find their roots in a common pool of mistakes. So, let’s see most common golang failures.

panic: runtime error: invalid memory address or nil pointer dereference

Golang uses pointers, which is a lot like its predecessor, C language. Mostly, the invalid memory address or null pointer dereference issue means that you try to access a variable pointing to nothing.
It could be due to no initialisation or a pointer reference overridden or deleted. This issue is very similar to the NullPointerException in Java.

The following snippet raises this kind of panic.

Here, the “f” variable is a pointer of the Foo type. “f” is never initialized. So, when we want to print the value of nested “bar” variable, we unintentionally raise the panic.

type Foo struct {
      bar int
}

func main() {
// We declare a variable f of Foo type
      var f *Foo

// We call the panic func attached, but we never initialised f before
      f.panic()
}

func (foo *Foo) panic() {
// f == foo == nil, so when we tried to access to bar, a panic is raised
fmt.Println(foo.bar)
// this code is never reached
      return
}

golang panic

panic: runtime error: index out of range

The “index out of range” issue is common when developers work with array or list structure.
This simply means that we tried to access an element/item that doesn’t exist in the structure.

The common mistakes are:

  • Trying to read the last element: The last element index is always the length of the structure minus 1 – the first item having the value 0.
  • In a loop: increase the current index position outside the boundaries.
func main() {

      array := make([]int, 10)
fmt.Printf("1st element:%d\n" , array[1])
      fmt.Printf("100th element: %d",array[100])

}

golang panic

panic: runtime error: slice bounds out of range

Pretty similar to the previous example. This time one of the slice bounds are out of the structure size.
A mistake example would be:

func main() {

      array := make([]int, 10)
fmt.Printf("Sub-array: %v\n", array[1:3])
      fmt.Printf("Sub-array size: %d",len(array[9:11]))

}

golang panic

panic: runtime error: integer divide by zero

Even in Go, dividing a number by zero is still impossible as shown right below:

func firePanic() {
      zero := 0
      fmt.Printf("Division: %d", 42 / zero)
}

golang panic

panic during panic

While looking around in the source code, I found an interesting case. I don’t know how to raise this kind of panic, but I really do like the cause. So let me share it with you 🙂

golang panic

Create your own golang panic

Before parting, I’d like to finish this post describing the way in which you can raise your own panic.
Sometimes, black magic appears, and you don’t know what to do. Raising a panic could be useful to stop the machine, take a breath and analyze what did happen before restarting the application.

It’s very simple.. Let’s have a look at this last snippet:

func main() {

      fmt.Printf("Keep calm, everything goes fine\n")
      panic(errors.New("Stop everything and start panicking"))      
      fmt.Printf("You should never see that")
      
}

And, here we are.
golang panic

The panic function takes a generic argument. So, you can pass everything on to this function. I recommend to set up a error though, as it’s more common.

I hope you enjoyed all of this golang panic moment we spent together, covering some basics on Golang panic, Defer and Recover. My teammates and I are writing a series of posts about Golang. I invite you to read all of them, especially the one about golang logging “our guide to a golang world” as we introduce a new approach: “code your logs” which is – in my humble opinion – a huge improvement in logging quality that all teams should bear in mind.

Would love to hear your comments or questions on Twitter.

Related Posts

Get notified when new content is published