W informatyce, zamknięcie (closure) jest funkcją, która posiada własne środowisko. W tym środowisku znajdują się co najmniej jedna lub więcej zmiennych związanych (tzw. zmienne wolne), których wartości są „zapamiętywane” i dostępne dla funkcji nawet po zakończeniu wykonania zakresu, w którym te zmienne zostały zadeklarowane. Innymi słowy, domknięcie to funkcja razem ze zbiorem powiązanych wartości kontekstowych, które funkcja widzi i może modyfikować.

Termin „closure” został wprowadzony przez Petera J. Landina w 1964 roku. Język programowania Scheme spopularyzował domknięcia po 1975 roku, a dziś wiele współczesnych języków programowania je obsługuje (np. JavaScript, Python, Ruby, Swift, Kotlin, Rust). Domknięcia są kluczowe w programowaniu funkcyjnym i w technikach opartych na funkcjach pierwszej klasy.

Jak działają domknięcia

  • Domknięcie powstaje, gdy funkcja (wewnętrzna) odwołuje się do zmiennych zewnętrznego zakresu (leksykalnego) — te zmienne stają się częścią środowiska domknięcia.
  • Środowisko domknięcia przechowuje wartości tych zmiennych w pamięci tak długo, jak długo istnieje odniesienie do funkcji (np. przez zwrócenie funkcji lub przypisanie jej do zmiennej).
  • Domknięcia respektują leksykalny (statyczny) zakres: zmienne wiązane są według miejsca deklaracji w kodzie, nie według miejsca wywołania.

Różnica między funkcją anonimową a domknięciem

Funkcje anonimowe (funkcje bez nazwy) bywają mylnie utożsamiane z domknięciami. Warto podkreślić różnicę:

  • Funkcja anonimowa to po prostu funkcja bez nazwy. Może, ale nie musi, być domknięciem.
  • Funkcja jest domknięciem wtedy i tylko wtedy, gdy posiada własne środowisko z co najmniej jedną zmienną związaną — niezależnie od tego, czy ma nazwę.
  • Nazwane zamknięcie (funkcja z nazwą, która tworzy domknięcie) dalej jest domknięciem, jeśli ma własne środowisko.

Przykłady

Przykład w JavaScript (licznik):

function makeCounter() {   let count = 0;   return function() {     count++;     return count;   }; } const c = makeCounter(); console.log(c()); // 1 console.log(c()); // 2 

W tym przykładzie zwrócona funkcja „zapamiętuje” zmienną count z otaczającego zakresu — dzięki temu stan jest utrzymywany między wywołaniami.

Przykład w Pythonie (użycie nonlocal):

def make_counter():     count = 0     def counter():         nonlocal count         count += 1         return count     return counter  c = make_counter() print(c())  # 1 print(c())  # 2 

W Pythonie, aby modyfikować zmienną zewnętrzną, trzeba użyć słowa kluczowego nonlocal (w przypadku zmiennej w bezpośrednim zewnętrznym zakresie) lub global dla zmiennej globalnej.

Typowe pułapki

  • Przechwytywanie zmiennych w pętli: w niektórych językach (np. starsze idiomy JS z var) wszystkie funkcje mogą przechwycić tę samą zmienną iteracyjną, co prowadzi do nieoczekiwanych wyników. Rozwiązaniem jest tworzenie nowych wiązań (np. użycie let w ES6 lub przekazanie wartości jako parametr domyślny).
  • Pamięć: domknięcia mogą utrzymywać duże struktury danych w pamięci dłużej niż oczekiwano, ponieważ referencje do zmiennych zewnętrznych zapobiegają ich zwolnieniu przez garbage collector. Należy uważać na niezamierzone utrzymywanie stanu.
  • Serializacja i debugowanie: domknięć nie da się zwykle łatwo zserializować (np. wysłać przez sieć), bo zawierają stan i kod. Debugowanie może być trudniejsze ze względu na ukryty stan.

Zastosowania

  • Enkapsulacja i prywatny stan: tworzenie funkcji fabrykujących obiekty z ukrytym stanem (np. modułowy pattern).
  • Factory functions i generatorów: konstruowanie funkcji skonfigurowanych z parametrami zachowanymi w domknięciu.
  • Kallbacki i programowanie asynchroniczne: przekazywanie funkcji, które mają dostęp do wcześniejszych zmiennych kontekstowych.
  • Funkcje wyższego rzędu: zwracanie funkcji spersonalizowanych pod kątem wcześniejszych argumentów.

Podsumowanie

Domknięcia to mechanizm pozwalający funkcjom przechowywać i używać stanu spoza ich lokalnego zakresu dzięki uchwyceniu zmiennych leksykalnych. Są potężnym narzędziem do organizowania kodu, ukrywania stanu i tworzenia elastycznych abstrakcji. Należy jednak pamiętać o typowych pułapkach związanych z zarządzaniem pamięcią i specyfiką wiązań zmiennych w danym języku.