Defer è un meccanismo unico di Go che consente di eseguire una funzione dopo il completamento della funzione corrente, anche in caso di panic. Storicamente è l'analogo delle costruzioni on-exit, ma in Go è implementato come uno stack di chiamate deferred. Un punto importante è che i parametri della funzione deferred vengono calcolati immediatamente al momento della dichiarazione di defer, non al momento della sua chiamata reale!
Problema — comportamento non ovvio: si potrebbe aspettare che i parametri vengano passati al momento dell'attivazione di defer, ma non è così. Questo porta spesso a bug quando si lavora con variabili mutabili o esterne.
Soluzione — tenere sempre presente che i parametri vengono calcolati immediatamente e che l'effetto della chiamata deferred si verifica successivamente.
Esempio di codice:
func f() { x := 5 defer fmt.Println(x) x = 10 } // Stamperà: 5, e non 10
Caratteristiche chiave:
Cosa stamperà il seguente codice? Perché?
func main() { i := 0 defer fmt.Println(i) i = 1 }
Risposta: stamperà 0. L'argomento della funzione fmt.Println viene salvato immediatamente al momento della dichiarazione di defer.
Influisce la modifica della variabile dopo la dichiarazione di defer sulla trasmissione del suo valore alla funzione?
No, non influisce: il calcolo avviene al momento della dichiarazione di defer:
defer fmt.Println(x) // Il valore x viene salvato adesso, non poi
È possibile fare defer per stampare infine l'ultimo stato della variabile?
Sì, utilizzando una funzione anonima (closure):
defer func() { fmt.Println(x) }() // catturerà il valore attuale di x al momento della chiamata deferred
Il codice viene chiamato in questo modo:
var f *os.File // ... defer f.Close()
Ma f viene assegnato successivamente, quindi panic da chiamata di puntatore nil!
Vantaggi:
Svantaggi:
Avvolgimento dell'azione di pulizia tramite una funzione deferred anonima con verifica:
defer func() { if f != nil { f.Close() } }()
Vantaggi:
Svantaggi: