ProgrammazioneSviluppatore Backend

Spiega le caratteristiche del funzionamento dei parametri delle funzioni deferred in Go: quando e come vengono calcolati i parametri per defer e come si differenzia dalla chiamata di una funzione al momento dell'attivazione di defer?

Supera i colloqui con l'assistente IA Hintsage

Risposta.

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:

  • I valori dei parametri per le funzioni deferred vengono calcolati al momento della dichiarazione di defer.
  • La funzione deferred viene sempre eseguita anche in caso di panic (se il panic non è stato intercettato precedentemente).
  • Se in deferred si dichiara una funzione anonima — si può ottenere accesso ai valori correnti delle variabili.

Domande insidiose.

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

Errori comuni e anti-pattern

  • Aspettarsi che il parametro sia attuale al momento della chiamata deferred.
  • Utilizzare defer con parametri insieme a variabili mutabili.
  • Stack di defer confusi senza una chiara descrizione delle dipendenze.

Esempio dalla vita reale

Caso negativo

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:

  • Scrittura breve, se la variabile è già inizializzata.

Svantaggi:

  • Se f == nil — panic.

Caso positivo

Avvolgimento dell'azione di pulizia tramite una funzione deferred anonima con verifica:

defer func() { if f != nil { f.Close() } }()

Vantaggi:

  • Sicurezza e assenza di panics.

Svantaggi:

  • Scrittura più lunga e "rumorosa".