Nanfeng

Notes on software development, code, and curious ideas

Metatables and Metamethods in Lua

A metatable changes how a Lua value reacts to predefined operations. When ordinary table addition is undefined, for example, Lua looks for an __add metamethod and calls it.

A vector type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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.__eq(a, b)
return a.x == b.x and a.y == b.y
end

function Vector:__tostring()
return ("Vector(%g, %g)"):format(self.x, self.y)
end

print(Vector.new(1, 2) + Vector.new(3, 4))

Arithmetic metamethods include __add, __sub, __mul, __div, __mod, __pow, and unary __unm, with additions depending on the Lua version. Comparison uses __eq, __lt, and __le. Do not mutate operands inside an arithmetic metamethod unless that behavior is explicitly part of the type’s contract.

__index and __newindex

When a key is absent, __index may be a table to continue lookup or a function to compute the result:

1
2
local defaults = { color = "blue", size = 12 }
local settings = setmetatable({}, { __index = defaults })

__newindex intercepts assignment to an absent key. Use rawget and rawset inside these handlers when access must bypass metamethods and avoid recursion.

1
2
3
4
5
6
7
local readOnly = setmetatable({}, {
__index = settings,
__newindex = function()
error("attempt to modify a read-only table", 2)
end,
__metatable = false
})

This proxy prevents normal assignment through readOnly, but it does not make the original settings table immutable if other code still holds it.

Other useful metamethods include __call for callable values, __len for length, __pairs in supported versions, __concat, and __gc for finalization of suitable objects. Metamethods are powerful language hooks; keep their behavior unsurprising and document any version-specific semantics.

+