Перейти до основного вмісту
Модуль 3 · Урок 5 із 515/50 у курсі~12 min
Модульні уроки (5/5)

`defer`: гарантоване очищення

defer відкладає виконання виклику до функції включення повертає. Він працює, навіть якщо функція виходить через паніку. Кілька defer у тій самій функції, що виконується в порядку LIFO (останній прийшов, перший вийшов).

Це ідіоматичний механізм для очищення ресурсу: закриття файлів, розблокування м'ютексів, звільнення підключень, відновлення стану.

Синтаксис і канонічний варіант використання

Go
f, err := os.Open(path)
if err != nil {
    return err
}
defer f.Close()   // chiamata SEMPRE all'uscita della funzione

// ... usa f liberamente ...
return nil

Типовий: f.Close(), mu.Unlock(), db.Close(), tx.Rollback(), recover().

Аргументи розглядаються негайно, виконання відкладено

Коли ви пишете defer foo(x), x оцінюється під час defer, не під час виконання:

Go
x := 10
defer fmt.Println(x)   // cattura 10
x = 99
// quando la funzione ritorna stampa "10", non "99"

Замовлення ЛІФО

Відкладає стек і виконання, починаючи з останнього:

Go
defer fmt.Println(1)
defer fmt.Println(2)
defer fmt.Println(3)
// output: 3, 2, 1

Це узгоджується із семантикою ресурсу: останній відкритий є першим бути закритим.

defer + recover: боротьба з панікою

defer — єдиний спосіб перехопити panic:

Go
func safe() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("recuperato:", r)
        }
    }()
    panic("boom")
}

Підводний камінь: defer в циклі

Розміщення defer f.Close() у циклі накопичує відкладення до кінця функція, а не кожної ітерації. У довгому циклі ви відкриваєте багато файлів і закривайте їх лише після завершення роботи функції: ризик витоку дескриптора файлу.

Go
// MALE: defer accumulati
for _, path := range paths {
    f, _ := os.Open(path)
    defer f.Close()   // chiude TUTTI alla fine della funzione esterna
    // ...
}

// MEGLIO: estrai in una funzione
for _, path := range paths {
    elabora(path)   // ogni chiamata ha il proprio scope di defer
}

Спробуйте

вправи#go.m3.l5.e1
Спроби: 0Завантаження…

Надрукуйте «inizio», потім відкладіть друк «fine» за допомогою «defer», потім надрукуйте «mezzo». Результат має бути: inizio / mezzo / fine.

Завантаження редактора…
Показати підказку

Відкладення запускається, коли main() збирається повернутися.

Рішення доступне після 3 спроб

вправи#go.m3.l5.e2
Спроби: 0Завантаження…

Напишіть три послідовні відкладення, які друкують 1, 2 і 3 у такому порядку в коді. Вихід буде 3 / 2 / 1 (LIFO).

Завантаження редактора…
Показати підказку

Три відкладення послідовно; пам'ятайте, що вони виконуються у зворотному порядку.

Рішення доступне після 3 спроб

Quiz#go.m3.l5.e3
Готовий

Що це друкує? (примітка: аргументи відкладення оцінюються негайно)

Go
x := 10
defer fmt.Println(x)
x = 99
Варіанти відповідей

Підведення підсумків

  • defer call() відкладає виконання, доки функція не повернеться.
  • Кілька відкладень = замовлення LIFO.
  • Аргументи оцінено негайно, виконання відкладено.
  • Використовується для детермінованого очищення: файлів, м'ютексів, з'єднань.
  • defer + recover() перехоплює panic (рідко, на кордонах).
  • Ніколи не defer всередині довгого циклу: він накопичується до повернення зовнішньої функції.