ProgrammatieSenior Go-ontwikkelaar

Vertel hoe Go closures (func literals/closures) implementeert en wat de beperkingen en kenmerken zijn van het gebruik van closures: waar ze worden opgeslagen, hoe variabelen worden vastgelegd, en wat het gedrag is van vastgelegde variabelen in verschillende scenario's?

Slaag voor sollicitatiegesprekken met de Hintsage AI-assistent

Antwoord

In Go kunnen anonieme functies (func literals) closures creëren, wat betekent dat ze toegang hebben tot variabelen uit de omringende scope, zelfs nadat deze is voltooid. Dergelijke closures alloceren geheugen in de heap als dit nodig is voor een correcte werking (gedetecteerd via escape-analyse).

Voorbeeld:

func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } a := adder() printf("%d\n", a(5)) // 5 printf("%d\n", a(10)) // 15

Kenmerken:

  • De closure vangt de variabelen van de externe scope bij referentie (en niet hun waarden op het moment van creatie).
  • Als de variabele buiten de closure wordt gewijzigd, zal de closure de nieuwe waarde zien.
  • Als de closure uit de functie terugkeert, zullen de vastgelegde variabelen blijven leven tot het einde van de closure.
  • Als de closure niet wordt gebruikt, kan de escape-analyse toestaan dat de variabelen niet naar de heap gaan.

Vraag met een valstrik

Wat zal deze code afdrukken?

func main() { fs := []func(){} for i := 0; i < 3; i++ { fs = append(fs, func() { fmt.Println(i) }) } for _, f := range fs { f() } }

Veel mensen zullen antwoorden dat het 0, 1, 2 zal afdrukken, echter zal het resultaat zijn:

3
3
3

Alle closures verwijzen naar dezelfde variabele i; na afloop van de lus is de waarde 3.

Correct: een kopie van de variabele vastleggen in de lus:

for i := 0; i < 3; i++ { v := i // nieuwe variabele fs = append(fs, func() { fmt.Println(v) }) }

Voorbeelden van echte fouten door onbekendheid met de subtiliteiten van het onderwerp


Verhaal

In een dynamisch routeringsproject werd een lus gebruikt om meerdere handlers te creëren via closures, die elk hun eigen pad moesten vastleggen. Als gevolg hiervan drukten alle handlers het laatste pad af — er was geen aparte variabele in elke closure gemaakt. De fout werd alleen ontdekt tijdens de integratie met de HTTP API.


Verhaal

Bij het testen van gelijktijdige toegang via goroutines binnen de lus, ving de closure een referentie op de index, niet een kopie. Dit veroorzaakte "willekeurige" effecten: gegevens werden niet in hun eigen array-slot geschreven, maar in het laatste.


Verhaal

In de functie voor het verzamelen van statistieken wijzigde de closure een gemeenschappelijke variabele uit de externe scope, terwijl de auteur een onafhankelijke teller voor elke taak verwachtte. Het probleem werd opgemerkt aan de inconsistent reproduceerbare som, die altijd gemeenschappelijk was, niet privé, ondanks de lokale logica.