In Go, la parola chiave defer ritarda l'esecuzione di una funzione fino al completamento della funzione circostante. Questo è utile per liberare risorse e per la finalizzazione. Tuttavia, l'uso di defer in combinazione con metodi che accettano puntatori (*T) o valori (T) presenta alcune sfumature inaspettate.
Per motivi di sicurezza del codice, Go è stato originariamente progettato per garantire la chiamata automatica di codice di pulizia, indipendentemente dal punto di uscita dalla funzione. Tuttavia, il tipo di receiver utilizzato (puntatore o valore) determina se l'oggetto del metodo che chiama defer viene copiato o se si modifica l'oggetto originale.
Se chiamiamo un metodo con un receiver di valore (T), un valore della struttura viene copiato in defer. Se chiamiamo un metodo con un receiver di puntatore (*T), il metodo lavora con l'oggetto originale. Di conseguenza, le modifiche apportate nel metodo defer tramite valore non saranno visibili, mentre attraverso il puntatore si rifletteranno sulla struttura esterna. Questo porta a errori difficili da individuare, specialmente quando si cerca di modificare lo stato di un oggetto tramite defer.
Quando si progettano metodi e si utilizza defer, è sempre importante scegliere consapevolmente il tipo di receiver. Le modifiche che devono persistere fino alla fine della funzione devono essere effettuate tramite puntatore.
Esempio di codice:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // metodo per valore defer func() { c.Value++ // aumenterà solo la copia fmt.Println("[Value receiver defer] Value:", c.Value) }() } func (c *Counter) IncPointer() { // metodo per puntatore defer func() { c.Value++ // aumenterà l'originale fmt.Println("[Pointer receiver defer] Value:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Value rimarrà 10 c.IncPointer() // Value diventerà 11 fmt.Println("Valore originale dopo le chiamate:", c.Value) }
Caratteristiche chiave:
1. Cambierà l'oggetto esterno se in defer viene chiamato un metodo che accetta un receiver per valore?
No, in defer l'oggetto originale non cambierà, poiché il metodo lavora con una copia della struttura.
2. Si può fare affidamento su defer per garantire la modifica dello stato della struttura tramite un metodo per valore?
No. È necessario utilizzare metodi con puntatore se si desidera riflettere le modifiche sull'oggetto originale.
3. Cosa succederà se dopo la dichiarazione di defer si modificano i campi della struttura?
Le azioni dipendono dal modo di passare il receiver: se il metodo/funzione riceve una copia, le modifiche dopo la dichiarazione di defer non saranno visibili nella funzione differita.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // catturerà il valore corrente c.Value = 42 // non influenzerà defer }
Abbiamo scritto una funzione di chiusura della connessione, aggiornando lo stato tramite defer con un metodo per valore. Si è scoperto che il flag di chiusura non veniva aggiornato.
Pro:
Contro:
Abbiamo utilizzato metodi con receiver di puntatore per la finalizzazione, modificando l'oggetto originale.
Pro:
Contro: