Metatables в Lua: просто о сложном
Разберём, что такое metatables в Lua, зачем они нужны и как с их помощью добавить таблицам «суперспособности».
Мы часто хотим, чтобы таблицы вели себя «по‑особенному»: складывались как векторы, подставляли значения по умолчанию, красиво печатались. ✨ Для этого в Lua есть metatables — скрытые сценаристы поведения таблиц. Ниже — краткий, практичный и безболезненный разбор.

🧩 Что такое metatables
Metatable — это обычная таблица, прикреплённая к другой таблице через setmetatable()
, которая описывает, как объект реагирует на операции: индексирование, сложение, сравнение, конкатенацию, вызов и т. д.
Думай о метатаблице как о «слое правил»: Lua проверяет ключи и операции — и, если находит соответствующий метаметод (например, __index
), использует его.
🧰 Зачем это нужно
Задача | Метаметод |
---|---|
Значения по умолчанию / прототип |
|
Контроль/запрет записи |
|
Переопределение сложения/умножения/… |
|
Красивый вывод |
|
Сравнение / длина / конкатенация |
|
Делаем таблицу «вызываемой» |
|
Защищаем метатаблицу |
|
Самый короткий пример: сложение таблиц как векторов
local mt = {
__add = function(a, b)
return { a[1] + b[1], a[2] + b[2] }
end
}
local v1 = setmetatable({1, 2}, mt)
local v2 = setmetatable({3, 4}, mt)
local sum = v1 + v2
print(sum[1], sum[2]) --> 4 6
Без метатаблиц v1 + v2
вызвало бы ошибку: Lua не знает, как складывать таблицы.
🧭 __index
: значения по умолчанию и «наследование»
__index
срабатывает, когда ключа в таблице нет. Его можно задать ссылкой на таблицу‑прототип или функцией.
Вариант 1: прототип
local defaults = { speed = 10, hp = 100 }
local player = setmetatable({}, { __index = defaults })
print(player.speed) --> 10 (берётся из defaults)
player.speed = 20 -- теперь свой speed у player
print(player.speed) --> 20
Вариант 2: автосоздание значений (ленивая инициализация)
local counts = setmetatable({}, {
__index = function(t, key)
local v = 0
rawset(t, key, v) -- записываем сразу, чтобы дальше было «как будто существовало всегда»
return v
end
})
counts["apples"] = counts["apples"] + 1
print(counts["apples"]) --> 1
Используем rawset
, чтобы не вызывать __newindex
и не попасть в рекурсию.
🛡️ __newindex
: контроль записи
local guarded = setmetatable({ x = 0, y = 0 }, {
__newindex = function(t, k, v)
if k == "x" or k == "y" then
rawset(t, k, v) -- безопасная запись без повторного вызова __newindex
else
error("Нельзя добавлять новое поле: " .. tostring(k), 2)
end
end
})
guarded.x = 10 -- ок
guarded.title = "oops" -- ошибка
Частая ошибка — внутри __newindex
написать t[k] = v
. Это снова вызовет __newindex
и приведёт к рекурсии. Используйте rawset
.
⚠️ Подводные камни
Метатаблицы не копируются при поверхностном копировании таблицы — навешивайте заново.
Слишком хитрые цепочки
__index
усложняют отладку. Держите модель простой.rawget/rawset
обходят метаметоды — это и сила, и риск. Используйте осознанно.
🚀 Продвинутые приёмы Metatables
📞 __call
: делаем «фабрику» объектов
Метаметод __call
позволяет вызывать таблицу как функцию — удобно для фабрик и DSL.
local greeter = setmetatable({}, {
__call = function(_, name) print("Привет, " .. name .. "!") end
})
greeter("Кодик") --> Привет, Кодик!
🔒 __metatable
: защита от изменений
Спрятать реальные метаметоды и запретить их менять можно так:
local obj = {}
local mt = { __metatable = "Доступ к метатаблице запрещён" }
setmetatable(obj, mt)
print(getmetatable(obj)) --> Доступ к метатаблице запрещён
-- getmetatable(obj).__index -- уже не получить
🎨 Красивый вывод и «магические» операторы
local mt = {
__tostring = function(t) return ("Point(%d, %d)"):format(t.x, t.y) end,
__len = function(t) return math.sqrt(t.x*t.x + t.y*t.y) end, -- длина вектора через #
__eq = function(a,b) return a.x==b.x and a.y==b.y end,
__add = function(a,b) return setmetatable({x=a.x+b.x, y=a.y+b.y}, mt) end,
}
local p = setmetatable({x=3, y=4}, mt)
print(p) --> Point(3, 4)
print(#p) --> 5
💬 Где бы ты применил metatables у себя — в игре на Roblox/Love2D, в конфиг‑системе или в мини‑DSL?
📚 Хочешь углубиться в тему?
В приложении Кодик ты найдёшь подробные уроки по Lua, пошаговые упражнения, разбор ошибок и удобную практику прямо в телефоне или браузере.
А если хочешь быть в курсе новостей, новых фич и полезных материалов — подписывайся на наш Telegram-канал. Там уютно, по делу и с любовью к коду ❤️