Замыкания и область видимости
Понимаем, как функция запоминает переменные своего окружения.
Замыкание — это функция, которая «помнит» переменные из того места, где была создана, даже после того как внешний код завершился.
Замыкания звучат пугающе, но это одна из самых мощных идей Lua. Они позволяют создавать функции с собственной «памятью».
Область видимости
Локальная переменная видна только в своём блоке — между объявлением и закрывающим end:
local function outer()
local secret = 42
print(secret) -- видно здесь
end
outer()
-- print(secret) -- здесь ошибка: secret не существуетВывод:
42
Захват переменной (upvalue)
Внутренняя функция видит переменные внешней. Когда она их использует, говорят, что она их захватывает:
local function makeCounter()
local count = 0
return function()
count = count + 1
return count
end
end
local next = makeCounter()
print(next())
print(next())
print(next())Вывод:
1 2 3
Переменная count живёт между вызовами, хотя makeCounter давно завершилась. Внутренняя функция «замкнула» её в себе.
Каждое замыкание независимо
local a = makeCounter()
local b = makeCounter()
print(a(), a(), b())Вывод:
1 2 1
У счётчиков a и b свои отдельные count — они не мешают друг другу.
Схема замыкания
makeCounter()
┌───────────────┐
│ count = 0 │ <-- захваченная переменная
│ │
│ return ───────┼──> функция помнит count
└───────────────┘Как работает под капотом
Когда Lua видит, что внутренняя функция использует внешнюю локальную переменную, она не уничтожает эту переменную после выхода из внешней функции. Вместо этого переменная превращается в upvalue — общую ячейку, на которую ссылается замыкание. Поэтому данные сохраняются между вызовами.
Частые ошибки
- Думать, что все замыкания делят одну переменную. На деле каждый вызов фабрики создаёт свежий набор upvalue.
- Создавать замыкания в цикле и удивляться, что все они захватили одно и то же значение (если переменная объявлена вне цикла).
- Считать, что захваченную переменную нельзя менять — её можно и читать, и записывать.
Итог
- Область видимости локальной переменной — от объявления до закрывающего
end. - Замыкание — функция, запомнившая внешние переменные (upvalue) своего окружения.
- Захваченная переменная живёт между вызовами и доступна для чтения и записи.
- Каждый вызов фабрики функций создаёт независимый набор захваченных переменных.