В информатиката затварянето е функция, която има собствена среда. В тази среда има поне една обвързана променлива (име, което има стойност, например число). Средата на затварянето съхранява обвързаните променливи в паметта между употребите на затварянето.

През 1964 г. Питър Джей Ландин дава на тази идея името "затваряне". След 1975 г. езикът за програмиране Scheme прави затварянето популярно. Много езици за програмиране, създадени след това, имат затваряния.

Анонимните функции (функции без име) понякога неправилно се наричат затваряния. Повечето езици, които имат анонимни функции, имат и затваряния. Една анонимна функция също е затваряне, ако има собствена среда с поне една обвързана променлива. Анонимна функция, която няма собствена среда, не е затваряне. Именувана затваряща функция не е анонимна.

Как работи затварянето

В основата си затварянето свързва функция с контекста (средата), в която тя е дефинирана. Тази среда съдържа стойности на локални променливи и параметри. Когато затварянето се извика по-късно, то все още има достъп до тези променливи, дори ако извикващият код вече е приключил изпълнението си.

Типичен механизъм включва следните елементи:

  • Дефиниране на функция вътре в друга функция: вътрешната функция "вижда" локалните променливи на външната.
  • Съхранение на средата: изпълнителната система (runtime) запазва необходимата информация в паметта, така че тя да не бъде освободена при излизане от външната функция.
  • Достъп при по-късни извиквания: затварянето използва съхранените стойности, когато се извиква извън оригиналния контекст.

Лексично (статично) и динамично обхващане

Важно е да се различава как се намират обвързаните променливи:

  • Лексично (статично) обхващане: връзките между променливи и функции се определят при компилация/четене на кода според неговата структура. Повечето съвременни езици (JavaScript, Python, Scheme) използват лексично обхващане.
  • Динамично обхващане: променливите се намират според текущия стек от извиквания по време на изпълнение. Това е по-рядко срещано и може да причини различно поведение на затварянията.

Примери

Примери помагат да се види практическата употреба. По-долу има схематични примери на популярни езици:

JavaScript (лексично обхващане):

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

Python (функции като обекти):

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

Scheme (класическа среда за затваряния):

(define (make-counter)   (let ((count 0))     (lambda ()       (set! count (+ count 1))       count)))

Приложения и полезни случаи

Затварянията се използват често за:

  • Инкапсулация на състояние: създаване на частни променливи, достъпни само през функции (мини-обекти без класове).
  • Фабрики за функции: генериране на персонализирани функции с предварително зададени параметри.
  • Колбекове и асинхронен код: предаване на функции, които запазват контекст за по-късно изпълнение.
  • Декоратори и частично приложение: модифициране на поведение чрез обвиване на функции.

Чести обърквания и грешки

  • Анонимни функции ≠ затваряния: както беше споменато, анонимната функция може да не е затваряне, ако не записва или няма нужда от външен контекст.
  • Променливи в цикли: при създаване на функции в цикъл в някои езици могат да възникнат неочаквани резултати, ако всички функции споделят една и съща външна променлива. Решенията включват създаване на нова локална променлива във всяка итерация или използване на лексически блокове.
  • Паметни изтичания: затварянията задържат референции към средата, което може да предотврати освобождаването ѝ от garbage collector и да доведе до увеличена паметна консумация, ако не се внимава.

Памет и жизнен цикъл

Когато затварянето продължи да бъде достъпно, средата му също остава в паметта. Много runtime среди използват garbage collection, за да освободят неизползваните среди, но ако затварянето е все още референцирано (например чрез глобална променлива или дълго живеещ обект), средата няма да бъде събрана.

Обобщение

Затварянето е мощна концепция, която позволява функции да запомнят и използват контекст от момента на тяхното създаване. Те са полезни за инкапсулация, конфигурирани функции и асинхронни модели. Разбирането на това как затварянето съхранява средата и какъв е жизненият ѝ цикъл помага да се избегнат често срещани грешки като неочаквано споделяне на състояние или паметни изтичания.