Metatables are the mechanism behind classes, proxies, default values and operator overloading in Lua.
Every table can have a metatable: an ordinary table whose specially named fields, metamethods, define what happens when an operation isn't handled normally. Set it with setmetatable(t, mt) (which returns t) and read it with getmetatable(t).
The most important ones:
__indexruns when a key is missing. If it is a table, Lua looks the key up there (and in that table's__index, and so on); if it is a function it is called with(t, key). This is how defaults and inheritance work.__newindexruns when assigning to a missing key; userawsetinside it to actually store.__add,__sub,__mul,__eq,__lt,__le,__concat,__unm,__lenoverload operators.__callmakes a table callable;__tostringcontrolstostringandprint;__closeand__gchandle cleanup.
rawget, rawset, rawequal and rawlen bypass metamethods, which you need inside them to avoid infinite recursion.
Strings share one metatable whose __index is the string table, which is why s:upper() works. Only tables (and full userdata from C) can have individual metatables set from Lua.
Metamethods are looked up with raw access in the metatable, so they must be present when the operation happens; adding __gc after setmetatable doesn't register it.
Example
local Vec = {}
Vec.__index = Vec
local function vec(x, y) return setmetatable({x = x, y = y}, Vec) end
Vec.__add = function(a, b) return vec(a.x + b.x, a.y + b.y) end
Vec.__eq = function(a, b) return a.x == b.x and a.y == b.y end
Vec.__tostring = function(v) return "(" .. v.x .. ", " .. v.y .. ")" end
function Vec.len(v) return math.sqrt(v.x ^ 2 + v.y ^ 2) end
local a = vec(3, 4)
print(tostring(a + vec(1, 1))) --> (4, 5)
print(a == vec(3, 4)) --> true
print(a:len()) --> 5.0 (found through __index)
local defaults = setmetatable({}, {__index = function(t, k) return 0 end})
defaults.hits = defaults.hits + 1
print(defaults.hits, defaults.misses) --> 1 0
local readonly = setmetatable({}, {__newindex = function() error("read-only", 2) end})
print(pcall(function() readonly.x = 1 end)) --> false ...read-onlyRemember
__indexhandles missing keys: a table to delegate, a function to compute- Arithmetic, comparison, call and tostring are all overridable
- Use
rawget/rawsetinside metamethods to avoid recursion
Watch out for
- Expecting
__indexto run for keys that exist: it only fires on missing keys.