All about defer, panic and recover

DEFER

A defer statement pushes a function call onto a stack. The stack of function calls are executed when the surrounding function returns. Syntactically, a defer statement is an ordinary function or a method call prefixed by the keyword defer.

func printStrings() {
1. fmt.Println("Beginning of the function")
2. defer fmt.Println("Deferred Call")
3. fmt.Println("End of the function")
4.}
OUTPUT:
Beginning of the function
End of the function
Deferred Call

When go recognises that the surrounding function exits(line number 4), it looks to see if there are any deferred function calls on the stack(line number 2 in the above example). If there are any, it simply executes them.

Rules that govern deferred statements are:

  • A deferred function’s arguments are evaluated when the defer statement is evaluated.

In the example below, the expression str is evaluated when the Println call is deferred. When the defer statement is encountered for the first time, then the function call along with its arguments is pushed onto the stack and executed when the surrounding function returns.

func sampleStrings() {
str := "begin"
defer fmt.Println(str)
str = "end"
}
OUTPUT:
begin
  • Deferred function calls are executed in LIFO(Last In First Out) order after the surrounding function returns.
func abc() {
defer fmt.Println("a")
defer fmt.Println("b")
defer fmt.Println("c")
}
OUTPUT:
c
b
a

PANIC

In go we usually don’t have exceptions like a lot of other languages have. A lot of cases that are considered exceptional in a lot of languages are considered normal in a go application. Eg: if you try to open a file that doesn’t exist, that’s actually a pretty normal response. It is reasonable to assume that you might try to open a file that doesn’t exist and so we return error values and don’t throw exceptions as that’s not considered exceptional in go. However there are cases, that gets go in a situation where it cannot figure out what to do and these cases are considered exceptional.

Go’s compiler catches many mistakes at compile time, but others, like an out-of-bounds array access or nil pointer dereference or division by zero, require checks at run time. When the Go runtime detects these mistakes, it panics.

Panic happens when all the deferred statements are executed. During a typical panic, normal execution stops, all deferred function calls in that go routine are executed, and the program crashes with a log message. This log message includes the panic value, which is usually an error message of some sort, and, for each go routine, a stack trace showing the stack of function calls that were active at the time of the panic. This log message has enough information to diagnose the root cause of the problem without running the program again.

func test() {
1. fmt.Println("start")
2. defer fmt.Println("deferred")
3. panic("something bad happened")
4. fmt.Println("end")
}
OUTPUT
start
deferred
panic: something bad happened

goroutine 1 [running]:
main.main()
/tmp/sandbox214463821/prog.go:4 +0xfe

In the above example, the stack trace given is for example purpose only. Please note that “end” is not printed(line number 4). As soon as the program encounters the panic line(line number 3), the execution of the surrounding function stops then and there and all the deferred statements are executed before the execution of panic.

RECOVER

Recover is a built-in function that handles the panicking go routine. It can be thought of as a catch construct in other languages.

Recover is only useful inside deferred functions. During normal execution, a call to recover will return nil and have no other effect. If the current go routine is panicking, a call to recover will capture the value given to panic and resume normal execution. The function that was panicking does not continue where it left off but returns normally. An example of recover is illustrated below:

func main() {
fmt.Println("begin")
panicker()
fmt.Println("end")
}
func panicker() {
fmt.Println("about to panic")
// Anonymous Function begins
defer func() {
if err := recover(); err != nil {
log.Println("Err: ", err)
}
}() //Anonymous function call(defined and called at the same time)
panic("something bad happened")
fmt.Println("done panicking")
}
OUTPUT:
begin
about to panic
2009/11/10 23:00:00 Err: something bad happened
end

In the above example, as soon as panic is encountered, the execution of the current or the surrounding function stops and control is transferred to the recover function inside the defer function which handles the panic appropriately. Please note that, the current function will stop its execution, but the higher functions in call stack(main() in the above example) will run as if nothing went wrong. Hence, you can see the “end” statement printed in the output.

That was all about defer, panic and recover. Comment down your queries in cases you have any. Will be happy to resolve them.