How to use defer in Go

Basics

To understand how defer works in Go, you need to know a little about its internals. When you use deferyou tell the Go compiler to defer execution of the specified function until the enclosing function completes. That is,defer allows you to manage resources and ensure that certain actions are performed regardless of how the current function completes – whether it is successful or an unexpected exit due to an error.

In Go, everyone defer places the function call in deferred call stack. This stack works on the principle LIFOthat is, the last added call is executed first.

Code example:

package main

import "fmt"

func main() {
    fmt.Println("Начало функции main")

    defer fmt.Println("Первый defer")
    defer fmt.Println("Второй defer")
    defer fmt.Println("Третий defer")

    fmt.Println("Конец функции main")
}

Conclusion:

Начало функции main
Конец функции main
Третий defer
Второй defer
Первый defer

As you can see from the output, deferred calls are executed in the reverse order of their declaration.

When there are multiple deferred calls, it is important to understand that they will be executed in reverse order. Let's look at an example that demonstrates this behavior:

package main

import "fmt"

func main() {
    defer fmt.Println("Завершение программы")
    
    for i := 1; i <= 3; i++ {
        defer fmt.Println("Отложенный вызов номер", i)
    }

    fmt.Println("Выполнение основного кода")
}

Conclusion:

Выполнение основного кода
Отложенный вызов номер 3
Отложенный вызов номер 2
Отложенный вызов номер 1
Завершение программы

defer allows you to structure code execution in reverse order. This can be useful, for example, for closing open connections one by one or releasing resources in the reverse order of their acquisition.

Examples of use

Resource management: closing files, connections, etc.

One of the main applications defer — is the management of resources that need to be closed or released after use. Operations such as closing files or network connections are ideal for deferbecause they must be executed regardless of how the function completes.

Let's say you need to write a program to process files. You open a file, read data from it, and then you have to close it. Defer allows you to ensure that the file will be closed in any case:

package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    file, err := os.Open("example.txt")
    if err != nil {
        fmt.Println("Ошибка при открытии файла:", err)
        return
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }

    if err := scanner.Err(); err != nil {
        fmt.Println("Ошибка чтения файла:", err)
    }
}

Here defer file.Close() ensures that the file will be closed when main will terminate execution, regardless of whether it is a normal exit or an error exit.

Working with network connections also sometimes requires attention to releasing resources:

package main

import (
    "fmt"
    "net/http"
)

func fetchData(url string) error {
    resp, err := http.Get(url)
    if err != nil {
        return fmt.Errorf("ошибка получения данных: %v", err)
    }
    defer resp.Body.Close()

    // обработка данных из ответа
    // ...

    return nil
}

func main() {
    err := fetchData("https://example.com")
    if err != nil {
        fmt.Println("Ошибка:", err)
    }
}

defer resp.Body.Close() Ensures that the HTTP connection is closed after work with it is finished.

Cleaning up resources in panic situations

Defer also good for handling situations where the program unexpectedly “panicking“.

Let's consider handling a panic situation with defer And recover:

package main

import (
    "fmt"
)

func safeDivide(a, b int) int {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Восстановление после паники:", r)
        }
    }()

    return a / b
}

func main() {
    fmt.Println("Результат деления:", safeDivide(10, 0))
}

defer used to set an anonymous function that calls recover in case of panic.

Logging and debugging using defer

Defer can also be used for logging and debugging.

Sometimes it is useful to log the entry and exit times of functions for debugging or monitoring:

package main

import (
    "fmt"
    "time"
)

func logExecutionTime(start time.Time, name string) {
    elapsed := time.Since(start)
    fmt.Printf("%s заняла %s\n", name, elapsed)
}

func processData() {
    defer logExecutionTime(time.Now(), "processData")

    // типо обработка данных
    time.Sleep(2 * time.Second)
    fmt.Println("Данные обработаны")
}

func main() {
    processData()
}

defer logExecutionTime(time.Now(), "processData") used to measure the execution time of a function processData.

Calculating the execution time of several functions:

package main

import (
	"fmt"
	"time"
)

func logFunctionTime() func() {
	start := time.Now()
	return func() {
		fmt.Printf("Время выполнения: %v\n", time.Since(start))
	}
}

func funcA() {
	defer logFunctionTime()()
	time.Sleep(1 * time.Second)
	fmt.Println("Выполнение функции A")
}

func funcB() {
	defer logFunctionTime()()
	time.Sleep(500 * time.Millisecond)
	fmt.Println("Выполнение функции B")
}

func main() {
	funcA()
	funcB()
}

Conclusion:

Выполнение функции A
Время выполнения: 1.0004321s
Выполнение функции B
Время выполнения: 500.6542ms

Each function call is wrapped in deferto accurately measure and display the time of its execution.

Resource managers

defer can be in the role of “manager“, which ensures that all resources are released correctly.

Example with transactions in the database:

package main

import (
    "database/sql"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
)

func executeTransaction(db *sql.DB) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }

    defer func() {
        if err != nil {
            tx.Rollback()
            fmt.Println("Транзакция откатилась")
        } else {
            tx.Commit()
            fmt.Println("Транзакция завершена")
        }
    }()

    _, err = tx.Exec("INSERT INTO users(name) VALUES('John')")
    if err != nil {
        return err
    }

    _, err = tx.Exec("INSERT INTO accounts(user_id) VALUES(LAST_INSERT_ID())")
    return err
}

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if err != nil {
        fmt.Println("Ошибка подключения к базе данных:", err)
        return
    }
    defer db.Close()

    err = executeTransaction(db)
    if err != nil {
        fmt.Println("Ошибка выполнения транзакции:", err)
    }
}

defer used to automatically roll back a transaction in case of an error.

Error handling

Can be used defer for error handling:

package main

import (
    "fmt"
    "os"
)

func openFile(filename string) (f *os.File, err error) {
    f, err = os.Open(filename)
    if err != nil {
        return nil, err
    }

    defer func() {
        if err != nil {
            f.Close()
            fmt.Println("Файл закрыт из-за ошибки")
        }
    }()

    // выполнение операций с файлом
    return f, nil
}

func main() {
    file, err := openFile("example.txt")
    if err != nil {
        fmt.Println("Ошибка:", err)
        return
    }
    defer file.Close()

    fmt.Println("Файл успешно открыт")
}

Accessing Function Return Values

It is possible to access the return values ​​of a function inside defer. This is useful when you want to change the return values ​​depending on the state of the function execution:

package main

import (
    "fmt"
)

func calculateSum(a, b int) (result int, err error) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Восстановление после паники:", r)
            err = fmt.Errorf("panic occurred")
        }
    }()

    result = a + b
    if result > 100 {
        panic("Сумма слишком велика")
    }

    return result, nil
}

func main() {
    sum, err := calculateSum(50, 60)
    if err != nil {
        fmt.Println("Ошибка:", err)
    } else {
        fmt.Println("Сумма:", sum)
    }
}

When used correctly defer allows you to write cleaner, safer, and more manageable code. The key is to be mindful of overhead and avoid common mistakes such as modifying variables after they are captured or overusing defer in cycles.

OTUS experts consider more practical tools for different programming languages ​​within the framework of practical online courses. You can find more details with the course catalog see the link.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *