Operator Overloading with Metamethods
One of Lua’s most powerful features is its ability to let you redefine how operators work with your own data types. Through metatables and metamethods, you can make tables respond to arithmetic operations, comparisons, and even function calls. This tutorial walks you through everything you need to know to start overloading operators in your Lua code.
Understanding Metatables and Metamethods
Every table in Lua can have an associated table called its metatable. The metatable contains special functions called metamethods that define behavior when certain operations are performed on the table. Think of a metatable as a table’s “personality” that tells Lua how to handle various operations.
When you write a + b in Lua, the interpreter normally expects both a and b to be numbers. But what if a is a vector represented as a table? Without customization, you’d get an error. With metatables, you can define what “adding” two vectors means.
Setting Up Your First Metatable
To use metamethods, you need three components: a table to hold your data, a metatable containing your custom functions, and the setmetatable function to connect them.
-- Create a table representing a 2D vector
local vector = {x = 10, y = 20}
-- Create a metatable with metamethods
local VectorMeta = {
__add = function(a, b)
return {x = a.x + b.x, y = a.y + b.y}
end
}
-- Attach the metatable to our vector
setmetatable(vector, VectorMeta)
-- Now we can "add" vectors!
local v2 = {x = 5, y = 10}
local result = vector + v2
print(result.x, result.y) -- Output: 15 30
This example creates a simple vector system where the + operator combines two vectors by adding their components together. The __add metamethod is one of seven arithmetic metamethods available.
Arithmetic Metamethods
Lua provides seven metamethods for arithmetic operations:
| Metamethod | Operation |
|---|---|
__add | + |
__sub | - |
__mul | * |
__div | / |
__mod | % |
__pow | ^ |
__unm | unary - |
Here’s a more complete example that implements a fraction class with multiple arithmetic operations:
local Fraction = {}
Fraction.__index = Fraction
function Fraction.new(numerator, denominator)
local self = setmetatable({}, Fraction)
self.n = numerator
self.d = denominator
return self
end
function Fraction:simplify()
-- Simple gcd for simplification
local a, b = self.n, self.d
while b ~= 0 do
a, b = b, a % b
end
self.n = self.n / a
self.d = self.d / a
return self
end
local FractionMeta = {
__add = function(a, b)
local n = a.n * b.d + b.n * a.d
local d = a.d * b.d
return Fraction.new(n, d):simplify()
end,
__mul = function(a, b)
local n = a.n * b.n
local d = a.d * b.d
return Fraction.new(n, d):simplify()
end,
__tostring = function(f)
return f.n .. "/" .. f.d
end
}
setmetatable(Fraction, {__call = function(_, n, d) return Fraction.new(n, d) end})
setmetatable(Fraction.prototype or {}, FractionMeta)
-- Usage
local f1 = Fraction(1, 2)
local f2 = Fraction(1, 3)
local sum = f1 + f2
print(sum) -- Output: 5/6
local product = f1 * f2
print(product) -- Output: 1/6
This fraction implementation demonstrates how multiple arithmetic metamethods work together to create a cohesive numeric type.
Comparison Metamethods
Lua supports three comparison metamethods: __eq for equality, __lt for less-than, and __le for less-than-or-equal. Notably, Lua does not provide __ge (greater-than-or-equal) — the interpreter automatically derives it by negating the __le result.
local Person = {}
Person.__index = Person
function Person.new(name, age)
return setmetatable({name = name, age = age}, Person)
end
local PersonMeta = {
__eq = function(a, b)
return a.name == b.name and a.age == b.age
end,
__lt = function(a, b)
return a.age < b.age
end,
__le = function(a, b)
return a.age <= b.age
end,
__tostring = function(p)
return p.name .. " (age " .. p.age .. ")"
end
}
setmetatable(Person, {__index = PersonMeta})
-- Create some people
local alice = Person.new("Alice", 30)
local bob = Person.new("Bob", 25)
local charlie = Person.new("Charlie", 30)
-- Comparison operators now work!
print(alice == charlie) -- Output: true (same name and age)
print(bob < alice) -- Output: true (bob is younger)
print(alice <= charlie) -- Output: true (same age)
-- Sorting works automatically
local people = {alice, bob, charlie}
table.sort(people)
for _, p in ipairs(people) do
print(p)
end
-- Output: Bob (age 25), Alice (age 30), Charlie (age 30)
The sorting example shows how implementing __lt enables your custom types to work with Lua’s built-in table.sort function.
Other Useful Metamethods
Beyond arithmetic and comparison, Lua provides several other metamethods for common operations:
__concat— defines the..(concatenation) operator__len— defines the#(length) operator__call— makes a table callable like a function__tostring— controls how the table appears inprintandtostring__index— customizes field access (like a getter)__newindex— customizes field assignment (like a setter)
Here’s an example combining several of these:
local Stack = {}
Stack.__index = Stack
function Stack.new()
return setmetatable({items = {}}, Stack)
end
function Stack:push(item)
table.insert(self.items, item)
end
function Stack:pop()
return table.remove(self.items)
end
local StackMeta = {
__call = function(s)
return s:pop()
end,
__len = function(s)
return #s.items
end,
__tostring = function(s)
return "Stack(" .. #s.items .. " items)"
end
}
setmetatable(Stack, {__call = function(_, ...) return Stack.new(...) end})
-- Usage
local myStack = Stack()
myStack:push("first")
myStack:push("second")
myStack:push("third")
print(#myStack) -- Output: 3
print(myStack) -- Output: Stack(3 items)
local item = myStack() -- Calls __call, which pops
print(item) -- Output: second
print(#myStack) -- Output: 2
This stack implementation demonstrates how __call lets tables behave like functions, while __len enables the # operator and __tostring provides readable output.
Common Pitfalls
When working with metatables, keep these gotchas in mind:
Plain tables don’t support operator overloading. If you create a table without setting a metatable, arithmetic operations will fail. Always use setmetatable(yourTable, yourMetaTable).
Metamethods are looked up on the metatable, not the table itself. The operation triggers the metamethod from the metatable, regardless of which table performs the operation.
Not all operations have metamethods. For example, there’s no __gt — use __lt with negated logic instead. Lua 5.4 defines 19 metamethods total.
See Also
- Tables Introduction — Foundation concepts for understanding metatables
- Inheritance in Lua — Using metatables for OOP patterns
Summary
Metatables and metamethods are the foundation of operator overloading in Lua. By attaching a metatable to your table and defining appropriate metamethods, you can make your custom types work naturally with Lua’s operator syntax. Whether you’re building vectors, fractions, complex numbers, or game-specific data structures, metamethods let you write expressive, readable code that feels native to the language.
The key steps are: create your data table, define a metatable with the desired metamethods, and connect them with setmetatable. From there, the possibilities are endless.