← Назад к вопросам

Для чего нужен defer в Go?

1.0 Junior🔥 281 комментариев
#Основы Go

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Назначение defer в Go

Определение

Defer — это оператор в Go, который откладывает выполнение функции до тех пор, пока окружающая функция не завершится. Это мощный инструмент для гарантированной очистки ресурсов и обработки ошибок.

func main() {
    defer fmt.Println("Я выполнюсь в конце")
    fmt.Println("Я выполнюсь первым")
    fmt.Println("Я выполнюсь вторым")
}
// Вывод:
// Я выполнюсь первым
// Я выполнюсь вторым
// Я выполнюсь в конце

Основное назначение: Cleanup и Resource Management

1. Закрытие файлов

func ReadFile(filename string) (string, error) {
    file, err := os.Open(filename)
    if err != nil {
        return "", err
    }
    defer file.Close()  // Гарантирует закрытие файла в любом случае
    
    // Даже если произойдёт паника, файл будет закрыт
    data, err := io.ReadAll(file)
    return string(data), err
}

2. Возврат соединения в пул (Connection Pool)

func QueryDatabase(db *sql.DB, id int) (*User, error) {
    // Получить соединение из пула
    conn, err := db.Conn(context.Background())
    if err != nil {
        return nil, err
    }
    defer conn.Close()  // Вернуть в пул
    
    var user User
    err = conn.QueryRowContext(
        context.Background(),
        "SELECT id, name FROM users WHERE id = $1",
        id,
    ).Scan(&user.ID, &user.Name)
    
    return &user, err
}

3. Блокировки (Mutexes)

var mu sync.Mutex
var count int

func Increment() {
    mu.Lock()
    defer mu.Unlock()  // Гарантированно разблокирует mutex
    
    count++
    // Даже если здесь произойдёт паника, mutex будет разблокирован
}

4. Транзакции БД

func TransferMoney(db *sql.DB, fromID, toID int, amount float64) error {
    tx, err := db.Begin()
    if err != nil {
        return err
    }
    defer tx.Rollback()  // На случай ошибки
    
    // Если всё хорошо, будет явный Commit
    // Если ошибка — Rollback выполнится автоматически
    
    if _, err := tx.Exec("UPDATE accounts SET balance = balance - $1 WHERE id = $2", amount, fromID); err != nil {
        return err
    }
    
    if _, err := tx.Exec("UPDATE accounts SET balance = balance + $1 WHERE id = $2", amount, toID); err != nil {
        return err
    }
    
    return tx.Commit().Err()
}

LIFO (Last In, First Out) порядок исполнения

func Order() {
    defer fmt.Println("1. Первый defer (последний)")
    defer fmt.Println("2. Второй defer")
    defer fmt.Println("3. Третий defer (первый)")
    fmt.Println("Основной код")
}
// Вывод:
// Основной код
// 3. Третий defer (первый)
// 2. Второй defer
// 1. Первый defer (последний)

// defer работает как LIFO стек

Обработка паник

Defer выполняется даже при панике:

func SafeOperation() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Поймана паника:", r)
            // Логирование, очистка и т.д.
        }
    }()
    
    // Некоторый код
    panic("Что-то пошло не так")  // Паника!
}
// Вывод: Поймана паника: Что-то пошло не так

Практический пример: HTTP обработчик

func HandleRequest(w http.ResponseWriter, r *http.Request) {
    // Логирование начала
    startTime := time.Now()
    defer func() {
        duration := time.Since(startTime)
        log.Printf("Запрос обработан за %v\n", duration)
    }()
    
    // Открыть БД соединение
    conn, err := db.Conn(r.Context())
    if err != nil {
        http.Error(w, "DB error", 500)
        return
    }
    defer conn.Close()
    
    // Получить данные
    data, err := conn.QueryContext(r.Context(), "SELECT * FROM users")
    if err != nil {
        http.Error(w, "Query error", 500)
        return
    }
    defer data.Close()
    
    // Обработка успешна
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(data)
}

Defer с функциями и замыканиями

func DeferWithClosure() {
    x := 10
    
    defer func() {
        fmt.Println("x:", x)  // Напечатает 20 (значение при выполнении)
    }()
    
    x = 20
}
// Вывод: x: 20

// ❌ Неправильно: значение захватывается по ссылке
func WrongDefer() {
    for i := 0; i < 3; i++ {
        defer fmt.Println(i)  // Напечатает 2, 1, 0 (последнее значение)
    }
}
// Вывод: 2, 1, 0

// ✅ Правильно: использовать параметры функции
func RightDefer() {
    for i := 0; i < 3; i++ {
        defer func(index int) {
            fmt.Println(index)
        }(i)  // Передаём i как параметр
    }
}
// Вывод: 2, 1, 0 (но логика ясна)

Ошибки при использовании defer

// ❌ ОШИБКА 1: Забыли вызвать функцию в defer
defer fmt.Println  // Это запланирует функцию, но не выполнит её
// ✅ Правильно:
defer fmt.Println("Сообщение")

// ❌ ОШИБКА 2: Игнорирование ошибок
defer file.Close()  // Ошибка закрытия может быть потеряна
// ✅ Правильно:
defer func() {
    if err := file.Close(); err != nil {
        log.Println("Close error:", err)
    }
}()

// ❌ ОШИБКА 3: Defer в цикле (memory leak)
for _, url := range urls {
    resp, _ := http.Get(url)
    defer resp.Body.Close()  // Не выполнится до конца функции!
}
// ✅ Правильно: вынесите в отдельную функцию
for _, url := range urls {
    if err := fetchURL(url); err != nil {
        log.Println(err)
    }
}

func fetchURL(url string) error {
    resp, _ := http.Get(url)
    defer resp.Body.Close()  // Выполнится после fetchURL
    return nil
}

Best Practices

✅ Правильно:

// Defer сразу после получения ресурса
resource, err := acquireResource()
if err != nil {
    return err
}
defer releaseResource(resource)

// Используйте именованные возвращаемые значения
func Process() (result string, err error) {
    defer func() {
        if err != nil {
            // Логирование
            log.Println(err)
        }
    }()
    // ...
}

Заключение

Defer используется для:

  • Гарантированной очистки ресурсов (файлы, соединения, блокировки)
  • Откладывания выполнения кода до конца функции
  • Обработки ошибок и паник
  • Логирования и профилирования

Deferзаписывает функции в стек и выполняет их в обратном порядке (LIFO) при выходе из функции, что делает код безопаснее и чище.