We often want tables to behave “in a special way”: to add up like vectors, to substitute default values, to print nicely. ✨ For this, Lua has metatables - hidden scriptwriters of table behavior. Below is a brief, practical and painless analysis.

🧩 What are metatables
Metatable — is a regular table attached to another table via setmetatable(), which describes how the object reacts to operations: indexing, addition, comparison, concatenation, call, etc.
Think of the metatable as a “layer of rules”: Lua checks the keys and operations — and, if it finds a corresponding metamethod (for example, __index), it uses it.
🧰 Why is it necessary
Task | Metamethod |
|---|---|
Default values / prototype |
|
Control/prohibition of recording |
|
Override addition/multiplication/... |
|
Beautiful output |
|
Comparison / length / concatenation |
|
Making a "called" table |
|
Protecting the metatable |
|
The shortest example: adding tables as vectors
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
Without metatables, v1 + v2 would cause an error: Lua does not know how to add tables.
🧭 __index: default values and "inheritance"
__index is triggered when there is no key in the table. It can be set by a reference to a prototype table or a function.
Option 1: prototype
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
Option 2: auto-create values (lazy initialization)
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
We use rawset to avoid calling __newindex and getting into recursion.
🛡️ __newindex: record control
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("You cannot add a new field: " .. tostring(k), 2)
end
end
})
guarded.x = 10 -- ок
guarded.title = "oops" -- ошибка
A common mistake is to write t[k] = v inside __newindex. This will again call __newindex and lead to recursion. Use rawset.
⚠️ Pitfalls
Metatables are not copied when surface copying a table, re-hang it.
Too tricky chains
__indexcomplicate debugging. Keep the model simple.rawget/rawsetbypass metamethods — this is both a strength and a risk. Use them consciously.
🚀 Advanced Metatables Techniques
📞 __call: making a "factory" of objects
The __call metamethod allows you to call a table as a function — convenient for factories and DSL.
local greeter = setmetatable({}, {
__call = function(_, name) print("Hi, " .. name .. "!") end
})
greeter("Code") --> Привет, Кодик!
🔒 __metatable: protection against changes
You can hide real metamethods and prohibit them from changing as follows:
local obj = {}
local mt = { __metatable = "Access to the metatable is denied" }
setmetatable(obj, mt)
print(getmetatable(obj)) --> Доступ к метатаблице запрещён
-- getmetatable(obj).__index -- уже не получить
🎨 Beautiful output and "magic" operators
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
💬 Where would you use metatables in your game on Roblox/Love2D, in the config system, or in mini-DSL?
📚 Do you want to delve into the topic?
In the attachment Code you will find detailed Lua lessons, step-by-step exercises, error analysis and convenient practice right on your phone or browser.
And if you want to be aware of the news, new features and useful materials - subscribe to our Telegram channel. It's cozy, businesslike and with love for code ❤️
