Defer

Going by literal meaning of the word Defer, means “put off an action or event to later time i.e. postponed”. In GO to the usage of keyword defer is to defer/postponed the execution.

So next thing comes to our mind is till what time will the execution be deferred? And answer is to the end of method or function in which it is used.

Let’s look at few examples to understand the usage of defer keyword.

package main

import (
	"fmt"
	"time"
)

func main() {
	fmt.Println("Start Processing")
	defer fmt.Println("Completed Processing")
	process()
}

func process(){
	fmt.Println("Processing will take approx 1 second")
	time.Sleep(1 * time.Second)
}

Output:

Start Processing
Processing will take approx 1 second
Completed Processing

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

This is a straight forward example, here we are printing simple statements to indicate Start of processing, Completion of processing and to mock processing we have function process which prints a statement and sleep for 1 second.

Key point to note is that statement for printing completion of processing is the 2nd line in function main just after printing start of processing and is prefixed with keyword defer. And from output we can see completion is printed at the end after processing of function process is finished.

So we just saw how prefixing call to print statement with keyword defer, actually deferred the processing to the end of the function in which it is declared.

Now let’s extend this example to also print time and verify time between start and completion is 1 second (function process we have explicitly called time.Sleep (1 * time.Second)).

package main

import (
	"fmt"
	"time"
)

func main() {
	startTime := time.Now()
	fmt.Printf("Started Processing at %s \n", formatTime(startTime))
	defer fmt.Printf("Completed Processing at %s, Time Taken %s \n", formatTime(time.Now()), time.Now().Sub(startTime))
	process()
}

func process(){
	fmt.Println("Processing will take approx 1 second")
	time.Sleep(1 * time.Second)
}

func formatTime(t time.Time) string {
	return t.Format("15:03:45")
}


Output:

Started Processing at 23:11:00 
Processing will take approx 1 second
Completed Processing at 23:11:00, Time Taken 0s   

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

Note: We have added new function formatTime to format Time in HH:MM:SS for better reading. and are using PrintF to allow printing of formatted time.

This is example, we have updated function main, added variable startTime which will be initialized to the current time at the start of function execution. We have also updated Start processing statement to print time. Similarly for completion statement we are printing current execution time and difference between current time and startTime using time.Sub() i.e. Time Taken. We know that function process sleeps for 1 second so we expect time taken to be 1 second.

Now if we look at the output especially 3rd line for completion statement whose execution was deferred and executed at the end of the function, we see the Time Taken as 0 seconds and even completion time and start time are same.

Though the completion of statement was printed post function process finished, but it was evaluated and all its parameter were resolved before function process was executed i.e. the order in which they appear in the code and hence completion statement has same time as Start Time and thereby time taken is also shown as 0 seconds.

Let’s look at the below updated code and see how we resolved this.

package main

import (
	"fmt"
	"time"
)

func main() {
	startTime := time.Now()
	fmt.Printf("Started Processing at %s \n", formatTime(startTime))
	defer printCompletionStatement(startTime)
	process()
}

func process(){
	fmt.Println("Processing will take approx 1 second")
	time.Sleep(1 * time.Second)
}

func formatTime(t time.Time) string {	
	return t.Format("15:03:45")
}

func printCompletionStatement(startTime time.Time) {	
	fmt.Printf("Completed Processing at %s, Time Taken %s \n", formatTime(time.Now()), time.Now().Sub(startTime))
}

Output:

Started Processing at 23:11:00
Processing will take approx 1 second
Completed Processing at 23:11:01, Time Taken 1s 

https://play.golang.org/p/8NCrA-ndzva

To resolve the above issue what we have done is we have introduced a function printCompletionStatement which will print the completion and updated the defer statement in function main to call this method instead of directly printing the statement.

Now, when defer statement will be evaluated before function process, GO will make a note that function printCompletionStatement is deferred and needs to be called at the end of function main. And same is reflected in output, which as expected shows the time taken as 1 second which is due to sleep in function process.

We saw usage of defer keyword and understood how it is evaluated by GO, but till now we saw examples with only 1 defer statement. So can we defer more than 1 statements?

And answer is YES, we can. Let take a look at example below:

package main

import (
	"fmt"	
)

func main() {	

	defer fmt.Printf("Deferred Statement %d processed\n", 1)
	defer fmt.Printf("Deferred Statement %d processed\n", 2)
	defer fmt.Printf("Deferred Statement %d processed\n", 3)

}

Output:

Deferred Statement 3 processed
Deferred Statement 2 processed
Deferred Statement 1 processed

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

As seen from the output all the 3 deferred statements are executed, but thing to note here is they are printed in reverse order or LIFO (Last In First Out) order.

Let’s us look at 1 more example below to understand how would GO manage to defer execution incase of error. We have 2 functions processWithError and printStatement. Function printStatement, prints statement is called 3 times, twice before calling processWithError. Function processWithError prints a statement, creates an error and will call panic to abruptly end execution.

ackage main

import (
	"errors"
	"fmt"	
)

func main() {	

	defer printStatement(1)
	defer printStatement(2)
	processWithError()
	defer printStatement(3)

}

func processWithError() {
	fmt.Println("Processing with error")
	err := errors.New("Panic with Error...")
	panic(err)	
}

func printStatement(i int){
	fmt.Printf("Deferred Statement %d processed\n", i)
}

Output

Processing with error
Deferred Statement 2 processed
Deferred Statement 1 processed
panic: Panic with Error...

https://play.golang.org/p/sNX-7HySRNJ

From output we can see that 2 print statements which are deferred before call to processWithError function are executed before abruptly ending execution due to call to panic. If you are familiar with JAVA, you can correlate this behaviour with keyword finally.

Conclusion

We learned about how defer keyword in GO can be used to postpone the execution to end of the function. Also we tried to understand how defer is evaluated by GO and how in case of multiple defer statements they are executed in LIFO order.