EZ

Eduzan

Learning Hub

Eduzan
Eduzan / Go Programming Language

Defer and Error Handling

The defer statement in Go is used to execute a function call just before the enclosing function returns. 

Example 1: Basic Usage of Defer

package main

import (
	"fmt"
	"time"
)

func logExecutionTime(start time.Time) {
	fmt.Printf("Execution time: %.2f seconds\n", time.Since(start).Seconds())
}

func performTask() {
	start := time.Now()
	defer logExecutionTime(start)
	time.Sleep(3 * time.Second)
	fmt.Println("Task completed")
}

func main() {
	performTask()
}

Explanation:
In this program, defer is used to measure the time taken by the performTask function. The start time is passed to the deferred call to logExecutionTime, which gets executed just before the function exits.

Output:

Task completed
Execution time: 3.00 seconds

Arguments Evaluation

The arguments of a deferred function are evaluated when the defer statement is executed, not at the time of function execution.

Example 2: Argument Evaluation

package main

import "fmt"

func printValue(x int) {
	fmt.Println("Deferred function received value:", x)
}

func main() {
	y := 7
	defer printValue(y)
	y = 15
	fmt.Println("Updated value of y before deferred execution:", y)
}

Explanation:
Here, y initially holds the value 7. When the defer statement is executed, the value of y at that moment is captured (7). Later, even though y is updated to 15, the deferred call uses the value captured at the time the defer statement was executed.

Output:

Updated value of y before deferred execution: 15
Deferred function received value: 7

Deferred Methods

Defer works not just with functions but also with methods.

Example 3: Deferred Method

package main

import "fmt"

type animal struct {
	name string
	kind string
}

func (a animal) describe() {
	fmt.Printf("%s is a %s.\n", a.name, a.kind)
}

func main() {
	dog := animal{name: "Buddy", kind: "Dog"}
	defer dog.describe()
	fmt.Println("Starting program")
}

Output:

Starting program
Buddy is a Dog.

Stacking Multiple Defers

Deferred calls are executed in Last In, First Out (LIFO) order.

Example 4: Reversing a String Using Deferred Calls

package main

import "fmt"

func main() {
	word := "Hello"
	fmt.Printf("Original Word: %s\n", word)
	fmt.Printf("Reversed Word: ")
	for _, char := range word {
		defer fmt.Printf("%c", char)
	}
}

Explanation:
Each deferred call to fmt.Printf is pushed onto a stack. When the function exits, these calls are executed in reverse order, printing the string backward.

Output:

Original Word: Hello
Reversed Word: olleH

Practical Uses of Defer

Defer is especially useful in scenarios where a function call must be executed regardless of the flow of the program.

Example 5: Simplified WaitGroup Implementation

package main

import (
	"fmt"
	"sync"
)

type rectangle struct {
	length int
	width  int
}

func (r rectangle) calculateArea(wg *sync.WaitGroup) {
	defer wg.Done()
	if r.length <= 0 || r.width <= 0 {
		fmt.Printf("Invalid dimensions for rectangle: %+v\n", r)
		return
	}
	fmt.Printf("Area of rectangle %+v: %d\n", r, r.length*r.width)
}

func main() {
	var wg sync.WaitGroup
	rects := []rectangle{
		{length: 10, width: 5},
		{length: -8, width: 4},
		{length: 6, width: 0},
	}

	for _, rect := range rects {
		wg.Add(1)
		go rect.calculateArea(&wg)
	}

	wg.Wait()
	fmt.Println("All goroutines completed")
}

Explanation:
The defer wg.Done() ensures the Done call is executed no matter how the function exits. This simplifies the code, making it easier to read and maintain.

Output:

Area of rectangle {length:10 width:5}: 50
Invalid dimensions for rectangle: {length:-8 width:4}
Invalid dimensions for rectangle: {length:6 width:0}
All goroutines completed
End of lesson.