In Go worden uitgestelde oproepen (defer) samen met anonieme closures gebruikt voor gegarandeerde opruiming of beëindiging van resources. Dit patroon maakt het mogelijk om opruimlogica te groeperen en fouten op een nette manier te behandelen, waardoor de code leesbaar en betrouwbaar is.
Achtergrond van de vraag:
Defer is overgenomen uit andere talen en maakt het leven van Go-ontwikkelaars aanzienlijk eenvoudiger. De combinatie van defer en closures is standaard geworden voor het zorgen voor opruiming van bestanden, verbindingen en andere externe resources in blokken waar veel uitgangen (return, panic) kunnen zijn.
Probleem:
Bij complexe opruimlogica voor resources (zoals bestanden, verbindingen, locks) moet worden gewaarborgd dat, zelfs in het geval van een fout of bij het verlaten van de functie, opruiming zal plaatsvinden. Onjuist gebruik kan leiden tot lekkages, een onjuist opruimorde of zinloze fouten.
Oplossing:
Gebruik defer met een anonieme functie (closure) om:
Voorbeeld code:
package main import ( "fmt" "os" ) func WriteFileDemo(filename string) (err error) { f, err := os.Create(filename) if err != nil { return } defer func() { cerr := f.Close() if cerr != nil && err == nil { err = cerr } }() // Werklijke logica met het bestand fmt.Fprintln(f, "Hello world") return // defer wordt uitgevoerd, ook als hier return is } func main() { if err := WriteFileDemo("test.txt"); err != nil { fmt.Println("Fout:", err) } }
Belangrijke kenmerken:
Wanneer worden de variabelen die binnen de deferred closure worden gebruikt vastgelegd — op het moment van declaratie van defer of bij de feitelijke oproep?
Ze worden vastgelegd op het moment van declaratie van defer, maar als de closure naar variabelen via hun referentie kijkt, zullen de waarden op het moment van uitvoering van defer worden gebruikt. Dit leidt soms tot onverwachte resultaten.
for i := 0; i < 3; i++ { defer func() { fmt.Println(i) }() // print 2, 2, 2 }
Is het mogelijk om waarden aan de closure door te geven via parameters, om zo referentie-capturing te vermijden?
Ja, je kunt parameters voor de anonieme functie declareren en de huidige waarden doorgeven — dan worden de waarden 'bevroren' als kopieën.
for i := 0; i < 3; i++ { defer func(n int) { fmt.Println(n) }(i) // print 2, 1, 0 }
Wat te doen als er een panic wordt ontdekt in de deferred closure? Hoe ga je daarmee om?
Binnen de closure gebruik je de constructie recover() om te voorkomen dat de panic naar buiten gaat en een zachte herstel van de functionaliteit te realiseren.
defer func() { if r := recover(); r != nil { log.Println("Hersteld:", r) } }()
Code opent meerdere bestanden, maar vergeet defer f.Close() te plaatsen. Bij een fout keert de controle terug, en blijven sommige bestanden niet opgeruimd, wat leidt tot resource leaks.
Voordelen:
Nadelen:
Gebruik van deferred closure voor alle verkoopoperaties: zorgvuldige sluiting van de bestandstroom en afhandeling van fouten, zelfs als het bestand niet volledig is geschreven.
Voordelen:
Nadelen: