defer werd in Go geïntroduceerd om het beheer van hulpbronnen (zoals bestanden, mutexen, verbindingen) te vereenvoudigen — voor elke situatie waarin moet worden gegarandeerd dat bewerkingen aan het einde van de functie worden uitgevoerd. Historisch gezien waren soortgelijke constructies aanwezig in andere talen (finally in Java, try-with-resources), maar Go implementeert een duidelijker en begrijpelijker patroon.
Probleem: Je moet er altijd zeker van zijn dat hulpbronnen worden vrijgegeven, zelfs als er een fout optreedt of er een panic gebeurt. Dubbele aanroep van het sluiten van een hulpbron of een lek — dit is een veelvoorkomend probleem in de klassieke programmeerstijl.
Oplossing: Alles wat via defer in een functie of methode is gedeclareerd, wordt op de aanroepstack geplaatst en wordt in omgekeerde volgorde uitgevoerd voordat je de functie verlaat. Dit garandeert de vrijgave van hulpbronnen, zelfs bij uitzonderingen (panic) of voortijdige return.
Voorbeeldcode:
func processFile() error { f, err := os.Open("filename.txt") if err != nil { return err } defer f.Close() // bestand wordt aan het einde gesloten // werken met het bestand return nil }
Belangrijkste kenmerken:
Worden de defer-functies uitgevoerd als er een panic in de functie optreedt?
Ja! Alle defer-functies worden aangeroepen, zelfs in het geval van een panic, dit is het belangrijkste mechanisme voor "finalisatie".
Wanneer worden de argumenten van de functie, doorgegeven aan defer, berekend?
Op het moment van declaratie van defer, en niet wanneer het daadwerkelijk wordt uitgevoerd. Daarom, als je variabelen gebruikt die daarna worden gewijzigd, is dit belangrijk om rekening mee te houden:
a := 1 defer fmt.Println(a) a = 2 // geeft 1 weer, niet 2
Hoe werkt defer binnen een lus? Leidt dit niet tot geheugenlekken?
Als in elke iteratie van de lus defer wordt gebruikt, worden alle defer-functies pas uitgevoerd na het beëindigen van de hele functie, en niet na iedere iteratie — de hele stack van defer-functies wordt opgehoopt, wat kan leiden tot overmatig geheugenverbruik.
for i := 0; i < 3; i++ { defer fmt.Println(i) }
Duizend bestanden worden in een lus geopend, en voor elk wordt defer gebruikt. Alle bestanden worden pas aan het einde van de hele functie gesloten en de hulpbronnen blijven vastzitten, wat leidt tot een "lek" — overschrijdingen van de limiet voor geopende bestanden.
Voordelen:
Nadelen:
In de lus worden lokale functies gebruikt, waar defer alleen voor het bereik van dit bestand wordt toegepast, en niet voor de hele handler:
for _, name := range fileNames { func() { f, _ := os.Open(name) defer f.Close() // werken met f }() }
Voordelen:
Nadelen: