Introduction
With the previous part (6), we finished basics in Go. Most of the topics covered in those parts are common in most programming languages. By hearing the topics covered in previous parts, we can relate them with other programming languages if we know any. The topics we are going to discuss in this part are Defer, Panic and Recover. I am not sure about the people reading this, but I haven't heard them before learning Go. But panic and recover are available in other programming languages as well but with different names. We will talk more about this later. Let's get into this one.
Defer
The functionality of defer is simple. It just delays the execution of a line for some time. Okay....How much time? It delays until all other lines in the function in which the defer line is residing are finished executing.
The syntax to use defer is defer <any valid line>
.'
Basic example to illustrate defer functionality
package main
import "fmt"
func main() {
defer fmt.Println("First line code, but comes last in output")
fmt.Println("Second line in code")
fmt.Println("Last line in code")
}
Output:
If you see the output, the line having defer printed last even though it is the first line in the function. That is what defer does. It delays the specified line of code until all other lines are executed in that function scope.
Program to illustrate multiple defer statements in same function
We saw how defer works. What if there are more than one defer statements in a function? Which line executes first and which one executes last?
To answer this, we need to understand what actually is happening when the defer line is being delayed. So, when we use defer before a line, when the control of program comes there, it pushes the defer line into a stack to store it temporarily. When the function execution comes to end, before it finishes, the lines pushed into the stack are popped out and executed one by one.
If you are aware how a stack works, it is a Last-In-First-Out data structure. Meaning, whichever item is pushed last into it, comes out first. So, in our context, the line which gets pushed into stack last, will get popped out first and executed first. See below code and output to see it practically.
package main
import "fmt"
func main() {
defer fmt.Println("First line") // goes first into defer stack
defer fmt.Println("Second line") // goes last into defer stack
fmt.Println("Third line")
fmt.Println("Last line")
}
Output:
In the above program, "First line" goes first into defer stack and "Second line" goes last. But in the output, after "Third line" and "Last line" got executed, "Second line" is executed first because it is the last line pushed into the stack.
Program to illustrate the variable values evaluation at defer line
I know the heading of this section is confusing. Even I don't understand what I have written. But just bear with me for this section.
Consider this situation where there is a variable in a function and that is being printed in a line. But there is a defer keyword before that line. After that line, we are changing the value of that variable. So, what value does the defer line prints? Initial value or Changed value? The line is using variable before changing the value. But also the line is being executed after changing the value. So, what value does it print?
The simple answer is when a defer line is pushed into the defer stack, it pushed along with the values of variables it is using but not the actual variable itself. So, no matter what happens to that variable after the line, the initial value is already pushed into defer stack along with defer line. Hence, it prints initial value.
I hope you understand the heading for this section a bit now.
package main
import "fmt"
func main() {
testVar := 10
defer fmt.Println("The value is: ", testVar) // should print 10 because the value is already evaluated
testVar += 1
}
Output:
Panic
This word is something unique to this Go language but the concept is not. We call these as exceptions in other languages. Yeah so, basically if any runtime error happens while executing a code, it creates panic in the execution and the executions terminates abruptly.
If you are already familiar with runtime errors, then fine. If not, just consider them as unexpected errors those may occur while program is running. A simple such error can be occurred during dividing two numbers. Consider a situation where we are taking input from user for two number and divide one with the other. If the user gives denominator as 0 and we divide with 0, it throws a runtime error. This creates panic in the execution and execution stops no matter if there is code after it.
Program to create panic by dividing with 0
package main
import "fmt"
func main() {
var a int
var b int
fmt.Scan(&a) // input 1
fmt.Scan(&b) // input 2
c := a / b // division
fmt.Println(c)
}
Output:
As you can see, since I have give second input as 0 and it divides with 0, the execution panics. Even though there is a print statement after that line, it won't execute it.
So, can a runtime error only create panic? No, we can create panic on our own using panic keyword. This is just like throwing errors in other languages.
Program to illustrate panic keyword
package main
import "fmt"
func main() {
fmt.Println("This line prints fine since it is before panic")
panic("Panicking just for fun")
fmt.Println("This line won't get printed since it is after panic")
}
Output:
Why are we creating panic for fun? We definitely don't do it for fun. But, I am not getting any idea on top of my head to justify that we don't do it for fun. I will write another article if I find one. Or you can comment down if you have ever thrown an error in code in a serious use-case.
Recover
In real world code bases, there are multiple functions running one after the other. If we some panic is being created in one function stops entire code execution, the next functions won't even run. We don't want it in most situations. For that we need a way to avoid stoppage of code execution even if panic happens.
This is where recover comes into picture. Just for comparison, this can be mapped to catch in other languages, but not exactly same.
recover is a function which returns why the panic is created if there is one or returns nil if no panic is there. We have to handle the situation depending on the return value of return function.
Program to illustrate how not to use recover
The motive of this program is to show how beginners may use recover in a wrong way. The recover in this program is totally useless.
package main
import "fmt"
func main() {
fmt.Println("This line prints fine since it is before panic")
panic("You thought recover under me can help you? Hahaha!!")
a := recover()
if a != nil {
fmt.Println("After recovery")
}
}
Output
Since, the nothing after panic can run and execution stops there, recover is useless in the above case.
Program to illustrate correct usage of recover
Okay fine, if we can't use recover after panic, there is only chance to use it before panic. But before panic, recover returns nil since there is won't be any panic at that time which is also of no use. Then how do we actually handle panic? This is where defer comes in handy.
Write the recover and other panic handling code inside a function and call the function before panic line with defer keyword. So, technically it is before panic, so executes and also executes after panic so that recover does not return nil
package main
import "fmt"
func catch() {
a := recover()
if a != nil {
fmt.Println("There is a panic, no worries, we handled it.")
}
}
func panicking() {
defer catch()
panic("I am afraid, I might get caught!")
}
func random() {
fmt.Println("Just there to show that program still continues")
}
func main() {
panicking()
random()
}
Output:
Conclusion
I hope you liked it. Next part will be on interfaces in go.
Do follow me to see them. Make sure you like and share if you felt this good.
Top comments (2)
I've been following this series for a long time. And I appreciated all of the examples and explanations. I'm looking forward to the next part interfaces.
Thank you so much. Next part is coming soon.