00
:
00
:
00
:
00
•Corso SEO AI - Usa SEOEMAIL al checkout per il 30% di sconto

Defer in Go

La parola chiave defer in Go permette di posticipare l’esecuzione di una funzione fino al momento in cui la funzione che la contiene termina. Questo meccanismo e fondamentale per la gestione delle risorse, come la chiusura di file, il rilascio di lock e la pulizia di connessioni. In questa guida vedremo come funziona defer e i pattern piu comuni.

Funzionamento di Base

Quando si usa defer prima di una chiamata a funzione, quella chiamata viene accodata ed eseguita appena prima che la funzione corrente ritorni.

package main

import "fmt"

func main() {
    fmt.Println("Inizio")
    defer fmt.Println("Questa viene eseguita alla fine")
    fmt.Println("Fine della funzione")
}

Output:

Inizio
Fine della funzione
Questa viene eseguita alla fine

L’istruzione defer registra la chiamata ma non la esegue subito. L’esecuzione avviene quando la funzione main sta per terminare.

Ordine LIFO (Last In, First Out)

Quando ci sono piu istruzioni defer, vengono eseguite in ordine inverso (LIFO - Last In, First Out), come una pila.

package main

import "fmt"

func main() {
    fmt.Println("Conto alla rovescia:")

    for i := 1; i <= 5; i++ {
        defer fmt.Println(i)
    }

    fmt.Println("Via!")
}

Output:

Conto alla rovescia:
Via!
5
4
3
2
1

Le funzioni differite vengono impilate e poi eseguite in ordine inverso.

Valutazione Immediata degli Argomenti

Gli argomenti passati a una funzione differita vengono valutati immediatamente, non al momento dell’esecuzione differita.

package main

import "fmt"

func main() {
    x := 10
    defer fmt.Println("Valore di x:", x) // x viene valutato ora, vale 10

    x = 20
    fmt.Println("x modificato a:", x)
}

Output:

x modificato a: 20
Valore di x: 10

Il valore 10 viene catturato al momento della dichiarazione defer, non quando viene eseguita.

Defer per la Chiusura di File

Il caso d’uso piu comune di defer e la chiusura di file subito dopo l’apertura, garantendo che il file venga chiuso anche in caso di errore.

package main

import (
    "fmt"
    "os"
)

func leggiFile(percorso string) error {
    file, err := os.Open(percorso)
    if err != nil {
        return err
    }
    defer file.Close() // Garantisce la chiusura del file

    // Operazioni sul file...
    buffer := make([]byte, 1024)
    n, err := file.Read(buffer)
    if err != nil {
        return err // Il file verra comunque chiuso grazie a defer
    }

    fmt.Printf("Letti %d byte\n", n)
    return nil
}

Posizionando defer file.Close() subito dopo il controllo dell’errore, la chiusura del file e garantita in qualsiasi percorso di esecuzione.

Defer con Mutex Unlock

Un altro pattern molto comune e il rilascio dei mutex.

package main

import (
    "fmt"
    "sync"
)

type ContatoreThread struct {
    mu     sync.Mutex
    valore int
}

func (c *ContatoreThread) Incrementa() {
    c.mu.Lock()
    defer c.mu.Unlock() // Rilascio garantito del lock

    c.valore++
}

func (c *ContatoreThread) Leggi() int {
    c.mu.Lock()
    defer c.mu.Unlock()

    return c.valore
}

func main() {
    contatore := &ContatoreThread{}
    contatore.Incrementa()
    contatore.Incrementa()
    fmt.Println("Valore:", contatore.Leggi())
}

Questo pattern previene i deadlock: anche se il codice tra Lock() e il ritorno genera un panic, il mutex viene rilasciato.

Defer con Funzioni Anonime

Per eseguire logica complessa in modo differito, si puo usare defer con una funzione anonima.

func elaboraDati() error {
    inizio := time.Now()

    defer func() {
        durata := time.Since(inizio)
        fmt.Printf("Elaborazione completata in %v\n", durata)
    }()

    // Logica di elaborazione...
    time.Sleep(100 * time.Millisecond)
    return nil
}

La funzione anonima cattura inizio per riferimento, ma time.Since(inizio) viene calcolato al momento dell’esecuzione differita.

Defer e Panic/Recover

defer e l’unico modo per eseguire codice durante un panic, rendendolo essenziale per il pattern recover.

package main

import "fmt"

func funzioneSicura() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recuperato dal panic:", r)
        }
    }()

    fmt.Println("Inizio operazione")
    panic("qualcosa e andato storto!")
    fmt.Println("Questa riga non viene raggiunta")
}

func main() {
    funzioneSicura()
    fmt.Println("Il programma continua normalmente")
}

Output:

Inizio operazione
Recuperato dal panic: qualcosa e andato storto!
Il programma continua normalmente

Pattern Comuni e Insidie

Defer in un Ciclo

Attenzione a usare defer nei cicli: le funzioni differite si accumulano e vengono eseguite solo alla fine della funzione, non del ciclo.

// PROBLEMA: i file restano aperti fino alla fine della funzione
func elaboraTuttiIFile(percorsi []string) error {
    for _, p := range percorsi {
        f, err := os.Open(p)
        if err != nil {
            return err
        }
        defer f.Close() // Si accumula ad ogni iterazione!
        // ...
    }
    return nil
}

// SOLUZIONE: estrarre in una funzione separata
func elaboraSingoloFile(percorso string) error {
    f, err := os.Open(percorso)
    if err != nil {
        return err
    }
    defer f.Close() // Viene eseguito alla fine di questa funzione
    // ...
    return nil
}

func elaboraTuttiIFile(percorsi []string) error {
    for _, p := range percorsi {
        if err := elaboraSingoloFile(p); err != nil {
            return err
        }
    }
    return nil
}

Defer e Valori di Ritorno con Nome

Con i ritorni nominati, una funzione differita puo modificare il valore di ritorno.

func operazioneConLog() (err error) {
    defer func() {
        if err != nil {
            fmt.Printf("Operazione fallita: %v\n", err)
        }
    }()

    // Se questa funzione restituisce un errore,
    // il defer lo intercetta e lo registra
    return fmt.Errorf("errore simulato")
}

Conclusione

defer e uno strumento potente e idiomatico in Go per la gestione delle risorse. L’ordine LIFO e la valutazione immediata degli argomenti sono aspetti fondamentali da comprendere. I pattern piu importanti sono la chiusura di file, il rilascio di mutex e il recover da panic. Ricorda di prestare attenzione all’uso di defer nei cicli e sfrutta i ritorni nominati quando serve controllare i valori di ritorno dalle funzioni differite.