Storicamente, il concetto di defer è stato introdotto in Go per garantire il rilascio sicuro delle risorse e la finalizzazione delle azioni indipendentemente da come si conclude l'esecuzione della funzione (normalmente o per panic). Tuttavia, l'interazione di defer con return e panic presenta alcune insidie fastidiose che spesso vengono trascurate anche dagli sviluppatori esperti.
Problema è che l'ordine di calcolo dei valori restituiti, il funzionamento dei named return values e la modifica di questi valori all'interno di defer differiscono notevolmente dal comportamento consueto in molti linguaggi. Inoltre, possono sorgere errori se in defer si tenta di modificare valori già calcolati, causando comportamenti inaspettati.
Soluzione — ricordare sempre: i valori restituiti dalla funzione vengono calcolati PRIMA dell'esecuzione di defer, ma se si utilizzano risultati nominati, questi possono essere modificati all'interno di defer prima del ritorno effettivo dalla funzione.
Esempio di codice:
func tricky() (res int) { defer func() { res = 42 // Cambia il valore restituito! }() return 10 } func main() { fmt.Println(tricky()) // Stampa 42, non 10 }
Caratteristiche chiave:
In che ordine vengono eseguite le funzioni defer?
Vengono eseguite rigorosamente in ordine inverso rispetto alla loro dichiarazione (stack — LIFO).
func f() { defer fmt.Println("1") defer fmt.Println("2") } // Stampa: 2, poi 1
Quando vengono calcolati i parametri per le funzioni defer — al momento della dichiarazione di defer o al momento della sua esecuzione?
I parametri per la funzione defer vengono calcolati al momento della dichiarazione di defer, non al momento della chiamata.
func f() { i := 1 defer fmt.Println(i) // verrà stampato 1, anche se i cambia dopo i = 2 }
Può defer modificare un risultato non nominato della funzione?
No. Solo i valori di ritorno nominati possono essere modificati in defer.
func f() int { defer func() { /* non posso modificare nulla */ }() return 5 }
Un giovane sviluppatore voleva registrare il codice di ritorno in defer e per errore ha modificato un valore di ritorno nominato, "schiacciando" così il vero risultato della funzione.
Pro:
Contro:
In un'altra situazione, defer veniva utilizzato solo per liberare risorse, registrazione e non modificava return, mentre i valori importanti venivano assegnati esplicitamente prima di return.
Pro:
Contro: