W Perl obsługiwane są anonimowe podprogramy i zamknięcia. Anonimowe podprogramy deklarowane są za pomocą sub bez nazwy i zwracają odniesienie do kodu. Zamknięcie to podprogram, który "przechwytuje" leksykalny zakres, w tym zmienne, które istniały w momencie jego utworzenia.
Przykład anonimowego podprogramu i closure:
sub make_incrementer { my $inc = shift; return sub { $_[0] + $inc }; } my $inc10 = make_incrementer(10); print $inc10->(5); # Wyświetli 15
Zamknięcia są aktywnie stosowane do tworzenia fabryk funkcji, generatorów i callbacków (na przykład w map/grep, obsługach zdarzeń).
Ważny punkt: jeśli zamknięcie odnosi się do zmiennych, które z kolei wskazują na samo zamknięcie (bezpośrednio lub przez strukturę), powstaje cykliczne odniesienie i może wystąpić wyciek pamięci.
Kiedy zmienne zamknięte w zamknięciu są zwalniane? Czy jest różnica w zachowaniu dla my i our?
Odpowiedź: Zmienne my, zamknięte wewnątrz zamknięcia, pozostają żywe tak długo, jak istnieje przynajmniej jedno odniesienie do samego zamknięcia. Nie są zwalniane po zakończeniu funkcji, ich czas życia to cały czas życia zamknięcia. W przypadku zmiennych our takiego zachowania nie ma — są dostępne dla wszystkich zamknięć i zwalniane po zakończeniu programu. Zapominając usunąć zamknięcie, można uzyskać wycieki pamięci.
Przykład problemu:
my $cref; { my $val = 5; $cref = sub { $val++ }; } # $val wciąż istnieje, dopóki żyje $cref
Historia
W aplikacji serwerowej dla każdego użytkownika tworzono zamknięcie-kontekst dla callback. Ale zamknięcie również wskazywało na strukturę użytkownika, co prowadziło do cyklicznego odniesienia. Z tego powodu garbage collector nie oczyszczał obiektów użytkownika nawet po wylogowaniu — wycieki pamięci rosły wykładniczo.
Historia
W aplikacji-demonie do przetwarzania zdarzeń w tle używano zamknięć z zamkniętymi zmiennymi-wskaźnikami, ale zapomniano wyczyścić tablice, na które one wskazywały. W efekcie, z powodu kilku zapomnianych zamknięć, przypadkowo gromadziły się stare dane wiadomości, aż do awarii demona konieczne było ręczne czyszczenie pamięci.
Historia
Programista próbował użyć zmiennej our w zamknięciu, oczekując "zamkniętego" zachowania — ale wszystkie zamknięcia dzieliły jedną zmienną, co prowadziło do wyścigów danych przy równoległym wykonywaniu. Błąd ujawnił się podczas jednoczesnej pracy użytkowników z różnymi parametrami (błędne obliczenia).