defer în Go

Amână un apel de funcție până la ieșirea din funcția curentă; folosit pentru închideri și curățenie.

Sintaxă
defer apelFunctie(argumente)
defer func() { ... }()

Ce face defer

defer programează un apel de funcție care rulează când funcția curentă se termină: după return, la final sau chiar la panic. Se folosește pentru curățenie scrisă imediat lângă deschidere: f, err := os.Open(...) urmat de defer f.Close(), mu.Lock() urmat de defer mu.Unlock().

Mai multe defer rulează în ordine inversă (ultimul amânat rulează primul), ca o stivă. Argumentele apelului amânat se evaluează imediat, în momentul lui defer, nu la final.

defer funcționează la nivel de funcție, nu de bloc: un defer într-o buclă rulează abia la ieșirea din funcție, nu la fiecare iterație. Împreună cu recover, un defer poate opri un panic.

Exemple

Exemplul 1: Ordinea inversă și argumentele evaluate imediat

i este copiat în momentul lui defer, deci se afișează 2, 1, 0.

package main

import "fmt"

func main() {
    fmt.Println("început")
    for i := range 3 {
        defer fmt.Println("defer", i)
    }
    fmt.Println("sfârșit")
}

Rezultat

început
sfârșit
defer 2
defer 1
defer 0

Exemplul 2: Închiderea unui fișier

defer f.Close() stă imediat după deschidere; fișierul se închide oricum s-ar termina funcția.

package main

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

func numaraRanduri(cale string) (int, error) {
    f, err := os.Open(cale)
    if err != nil {
        return 0, err
    }
    defer f.Close()

    n := 0
    sc := bufio.NewScanner(f)
    for sc.Scan() {
        n++
    }
    return n, sc.Err()
}

func main() {
    if err := os.WriteFile("lista.txt", []byte("pâine\nlapte\nouă\n"), 0o644); err != nil {
        fmt.Println(err)
        return
    }
    n, err := numaraRanduri("lista.txt")
    fmt.Println("rânduri:", n, "eroare:", err)
    _, err = numaraRanduri("lipsa.txt")
    fmt.Println("eroare:", err)
}

Rezultat

rânduri: 3 eroare: <nil>
eroare: open lipsa.txt: no such file or directory

Exemplul 3: Măsurarea duratei unei funcții

Durata reală diferă la fiecare rulare; aici funcția „durează” un număr fix de pași, ca rezultatul să fie același.

package main

import "fmt"

func urmareste(nume string) func() {
    fmt.Println("intru în", nume)
    return func() { fmt.Println("ies din", nume) }
}

func calcul() int {
    defer urmareste("calcul")() // primul apel rulează acum, al doilea la final
    suma := 0
    for i := 1; i <= 100; i++ {
        suma += i
    }
    fmt.Println("suma calculată:", suma)
    return suma
}

func main() {
    fmt.Println("rezultat:", calcul())
}

Rezultat

intru în calcul
suma calculată: 5050
ies din calcul
rezultat: 5050

Greșeli frecvente

  • defer f.Close() înainte de verificarea erorii de la os.Open: dacă f este nil, închiderea dă panic. Verifică întâi err.
  • defer într-o buclă care deschide multe fișiere: toate rămân deschise până la finalul funcției. Mută corpul buclei într-o funcție separată.
  • Așteptarea ca argumentele să fie citite la final: defer fmt.Println(x) afișează valoarea lui x din momentul lui defer.

Comenzi înrudite

  • panic și recover: panic oprește execuția normală pentru erori grave; recover, apelat într-un defer, poate opri panic-ul.
  • func: Declară o funcție: parametri cu tip după nume, unul sau mai multe rezultate, metode și funcții anonime.
  • return: Încheie funcția curentă și trimite apelantului valorile rezultat (zero, una sau mai multe).