In Go stelt het sleutelwoord defer uitstel van de uitvoering van een functie tot het einde van de omringende functie in. Dit is handig voor het vrijgeven van middelen en het afronden. Het gebruik van defer in combinatie met methoden die pointers (*T) of waarden (T) accepteren, heeft echter enkele niet-evidente nuances.
Vanwege de beveiliging van de code is Go oorspronkelijk ontworpen om de automatische aanroep van opschoningscode te garanderen, ongeacht de plaats van het verlaten van de functie. Echter, het type van de receiver (pointer of waarde) bepaalt of het object van de aanroepende methode wordt gekopieerd of dat het originele object wordt gewijzigd.
Als we een methode met een receiver-waarde (T) aanroepen, wordt de waarde van de structuur gekopieerd in defer. Als een methode met een receiver-pointer (*T) wordt aangeroepen, werkt de methode met het originele object. Dit leidt ertoe dat wijzigingen in de gegevens van de defer-methode per waarde onopgemerkt blijven, terwijl wijzigingen per pointer zich op de externe structuur weerspiegelen. Dit leidt tot moeilijk te traceren fouten, vooral wanneer je probeert de status van een object via defer te wijzigen.
Bij het ontwerpen van methoden en het gebruik van defer, moet je altijd bewust het type receiver kiezen. Wijzigingen die tot het einde van de functie moeten overleven, moeten via een pointer worden uitgevoerd.
Voorbeeldcode:
package main import "fmt" type Counter struct { Value int } func (c Counter) IncValue() { // methode per waarde defer func() { c.Value++ // alleen de kopie wordt verhoogd fmt.Println("[Value receiver defer] Waarde:", c.Value) }() } func (c *Counter) IncPointer() { // methode per pointer defer func() { c.Value++ // het origineel wordt verhoogd fmt.Println("[Pointer receiver defer] Waarde:", c.Value) }() } func main() { c := Counter{Value: 10} c.IncValue() // Waarde blijft 10 c.IncPointer() // Waarde wordt 11 fmt.Println("Originele Waarde na aanroepen:", c.Value) }
Belangrijke kenmerken:
1. Zal het externe object veranderen als we in defer een methode aanroepen die een receiver per waarde accepteert?
Nee, in defer zal het originele object niet veranderen, omdat de methode met een kopie van de structuur werkt.
2. Kun je op defer vertrouwen voor een gegarandeerde wijziging van de status van een structuur via een methode per waarde?
Nee. Je moet methoden met een pointer gebruiken als je wijzigingen op het originele object wilt weerspiegelen.
3. Wat gebeurt er als we de velden van de structuur na de declaratie van defer wijzigen?
De acties zijn afhankelijk van de manier van doorgeven van de receiver: als de methode/functie een kopie ontvangt, zullen wijzigingen na de declaratie van defer niet zichtbaar zijn in de uitgestelde functie.
func (c Counter) Demo() { defer fmt.Println("defer c.Value:", c.Value) // pakt de huidige waarde c.Value = 42 // heeft geen invloed op defer }
We schreven een functie voor het sluiten van een verbinding, waarbij we de status bijwerkten via defer met een methode per waarde. Het bleek dat de sluitingsindicator niet werd bijgewerkt.
Voordelen:
Nadelen:
We gebruikten methoden met een receiver-pointer voor afronding, waarbij het originele object werd gewijzigd.
Voordelen:
Nadelen: