Metatables and Metamethods

16.1 Basics

local t = {}
local mt = {}
setmetatable(t, mt)                   -- returns t
print(getmetatable(t) == mt)          --> true
-- Strings share a metatable whose __index is the string table: ("x"):upper()
print(getmetatable("").__index == string)   --> true

16.2 Metamethods Reference

Reading a missing key (t[k]): a table or a function (t, k) Assigning a missing key (t[k] = v)

Calling the table like a function: t(...) tostring(t) / print(t)

Type name used in error messages and by tostring __eq, __lt, __le

==, <, <= (__le no longer falls back to __lt in 5.4)

__add, __sub, __mul, __div, __mod, __pow, __unm, __idiv

__band, __bor, __bxor, __bnot, __shl, __shr Bitwise (5.3+) To-be-closed variables (5.4) Finalizer when collected Weak tables ("k", "v", "kv")

Protects the metatable (getmetatable returns this value; setmetatable errors)

Customizes pairs (5.2+; deprecated in 5.4)

16.3 Operator Overloading: A Vector Type

local Vector = {}
Vector.__index = Vector

function Vector.new(x, y) return setmetatable({x = x, y = y}, Vector) end
function Vector.__add(a, b) return Vector.new(a.x + b.x, a.y + b.y) end
function Vector.__sub(a, b) return Vector.new(a.x - b.x, a.y - b.y) end
function Vector.__mul(a, b)
    if type(a) == "number" then return Vector.new(a * b.x, a * b.y) end
    if type(b) == "number" then return Vector.new(a.x * b, a.y * b) end
    return a.x * b.x + a.y * b.y                       -- dot product
end
function Vector.__unm(v) return Vector.new(-v.x, -v.y) end
function Vector.__eq(a, b) return a.x == b.x and a.y == b.y end
function Vector.__lt(a, b) return a:length() < b:length() end
function Vector.__le(a, b) return a:length() <= b:length() end
function Vector.__len(v) return 2 end
function Vector.__tostring(v) return string.format("(%g, %g)", v.x, v.y) end
function Vector.__concat(a, b) return tostring(a) .. tostring(b) end
function Vector:length() return math.sqrt(self.x ^ 2 + self.y ^ 2) end

local a, b = Vector.new(1, 2), Vector.new(3, 4)
print(a + b, a - b, a * 2, 3 * a, a * b, -a)   --> (4, 6) (-2, -2) (2, 4) (3, 6) 11 (-1, -2)
print(a == Vector.new(1, 2), a < b, #a)        -
…

16.4 __index and __newindex Patterns

-- Default values
local defaults = setmetatable({}, {__index = function() return 0 end})
defaults.x = defaults.x + 1        -- missing keys read as 0
print(defaults.x, defaults.y)      --> 1  0

-- Read-only table
local function readonly(t)
    return setmetatable({}, {
        __index = t,
        __newindex = function() error("attempt to modify a read-only table", 2) end,
        __len = function() return #t end,
        __pairs = function() return next, t, nil end,
        __metatable = false,
    })
end
local days = readonly{"Mon", "Tue", "Wed"}
-- days[1] = "Sun"   --> error

-- Tracking access (proxy)
local function track(t)
    return setmetatable({}, {
        __index = function(_, k) print("read " .. tostring(k)); return t[k] end,
        __newindex = function(_, k, v) print("write " .. tostring(k)); t[k] = v end,
    })
end

-- rawget / rawset / rawequal / rawlen bypass metamethods
local p = track({})
rawset(p, "direct", 1)     -- no "write" message
print(rawget(p, "direct"))

-- Callable tables
local counter = setmetatable({n = 0}, {__call = function(self, inc) self.n = self.n + (inc or 1); return self.n end})
print(counter(), counter(5))       --> 1  6

-- Memoizing tab
…