Metatables in Lua
Metatables are one of Lua's most powerful features, allowing you to change the behavior of tables. With metatables, you can overload operators, customize table access, create object-oriented systems, and implement advanced metaprogramming patterns. Understanding metatables is key to mastering Lua. Let's explore this powerful feature!
What are Metatables?
A metatable is a table that defines how another table behaves in certain situations:
local t = {} -- Regular table
local mt = {} -- Metatable
setmetatable(t, mt) -- Attach metatable to t
-- Check metatable
local meta = getmetatable(t)
print(meta == mt) -- true
setmetatable(table, metatable)- Set a table's metatablegetmetatable(table)- Get a table's metatable
The __index Metamethod
Called when accessing a non-existent key:
local defaults = {
name = "Guest",
age = 0
}
local person = {}
setmetatable(person, {
__index = defaults
})
print(person.name) -- "Guest" (from defaults)
print(person.age) -- 0 (from defaults)
person.name = "Alice"
print(person.name) -- "Alice" (from person)
__index as a Function
local t = {}
setmetatable(t, {
__index = function(table, key)
return "Key '" .. key .. "' not found"
end
})
print(t.anything) -- "Key 'anything' not found"
Click Run to execute your code
The __newindex Metamethod
Called when setting a new key:
local t = {}
local storage = {}
setmetatable(t, {
__newindex = function(table, key, value)
print("Setting " .. key .. " = " .. tostring(value))
storage[key] = value
end,
__index = storage
})
t.name = "Alice" -- Prints: Setting name = Alice
print(t.name) -- Alice (from storage)
Read-Only Tables
local function readOnly(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(table, key, value)
error("Attempt to modify read-only table")
end
}
setmetatable(proxy, mt)
return proxy
end
local config = readOnly({debug = true, timeout = 30})
print(config.debug) -- true
-- config.debug = false -- Error!
Click Run to execute your code
Arithmetic Metamethods
Overload arithmetic operators:
local Vector = {}
function Vector:new(x, y)
local v = {x = x, y = y}
setmetatable(v, {__index = self})
return v
end
-- Addition
function Vector.__add(a, b)
return Vector:new(a.x + b.x, a.y + b.y)
end
-- Subtraction
function Vector.__sub(a, b)
return Vector:new(a.x - b.x, a.y - b.y)
end
-- Multiplication (scalar)
function Vector.__mul(a, scalar)
return Vector:new(a.x * scalar, a.y * scalar)
end
local v1 = Vector:new(1, 2)
local v2 = Vector:new(3, 4)
local v3 = v1 + v2 -- {x = 4, y = 6}
Available Arithmetic Metamethods
| Metamethod | Operator | Description |
|---|---|---|
__add |
+ |
Addition |
__sub |
- |
Subtraction |
__mul |
* |
Multiplication |
__div |
/ |
Division |
__mod |
% |
Modulo |
__pow |
^ |
Exponentiation |
__unm |
- |
Unary negation |
Click Run to execute your code
Comparison Metamethods
Overload comparison operators:
local Set = {}
function Set:new(items)
local s = {items = items or {}}
setmetatable(s, {
__index = self,
__eq = function(a, b)
-- Check if sets are equal
for k in pairs(a.items) do
if not b.items[k] then return false end
end
for k in pairs(b.items) do
if not a.items[k] then return false end
end
return true
end,
__lt = function(a, b)
-- Check if a is subset of b
for k in pairs(a.items) do
if not b.items[k] then return false end
end
return true
end
})
return s
end
local s1 = Set:new({a = true, b = true})
local s2 = Set:new({a = true, b = true, c = true})
print(s1 < s2) -- true (s1 is subset of s2)
print(s1 == s2) -- false
The __tostring Metamethod
Customize string representation:
local Person = {}
function Person:new(name, age)
local p = {name = name, age = age}
setmetatable(p, {
__index = self,
__tostring = function(self)
return self.name .. " (" .. self.age .. " years old)"
end
})
return p
end
local person = Person:new("Alice", 25)
print(person) -- Alice (25 years old)
Click Run to execute your code
The __call Metamethod
Make tables callable like functions:
local Counter = {}
function Counter:new(start)
local c = {count = start or 0}
setmetatable(c, {
__index = self,
__call = function(self)
self.count = self.count + 1
return self.count
end
})
return c
end
local counter = Counter:new(0)
print(counter()) -- 1
print(counter()) -- 2
print(counter()) -- 3
The __len Metamethod
Customize the # operator:
local CustomArray = {}
function CustomArray:new()
local arr = {items = {}, count = 0}
setmetatable(arr, {
__index = self,
__len = function(self)
return self.count
end
})
return arr
end
function CustomArray:add(value)
self.count = self.count + 1
self.items[self.count] = value
end
local arr = CustomArray:new()
arr:add("a")
arr:add("b")
print(#arr) -- 2
Click Run to execute your code
Practical Examples
Class System
local Class = {}
function Class:new()
local class = {}
class.__index = class
function class:create(...)
local instance = setmetatable({}, self)
if instance.init then
instance:init(...)
end
return instance
end
return class
end
-- Usage
local Animal = Class:new()
function Animal:init(name)
self.name = name
end
function Animal:speak()
print(self.name .. " makes a sound")
end
local dog = Animal:create("Buddy")
dog:speak() -- Buddy makes a sound
Property Tracking
local function tracked(t)
local proxy = {}
local mt = {
__index = t,
__newindex = function(table, key, value)
print("Changed: " .. key .. " = " .. tostring(value))
t[key] = value
end
}
setmetatable(proxy, mt)
return proxy
end
local person = tracked({name = "Alice"})
person.age = 25 -- Prints: Changed: age = 25
Click Run to execute your code
Complete Metamethod Reference
| Metamethod | Purpose |
|---|---|
__index |
Table access (read) |
__newindex |
Table access (write) |
__call |
Call table as function |
__tostring |
String conversion |
__len |
Length operator # |
__add, __sub, __mul, __div |
Arithmetic operators |
__eq, __lt, __le |
Comparison operators |
__concat |
Concatenation .. |
__pairs, __ipairs |
Custom iteration |
Practice Exercise
Try these metatable challenges:
Click Run to execute your code
Summary
In this lesson, you learned:
- What metatables are and how to use them
__indexfor customizing table access__newindexfor controlling table modification- Arithmetic metamethods for operator overloading
- Comparison metamethods for custom comparisons
__tostringfor string representation__callto make tables callable- Practical applications: classes, read-only tables, property tracking
What's Next?
You've mastered metatables! Next, we'll explore iterators—how to create custom iteration patterns for your tables. You'll learn about stateless and stateful iterators, and how to build powerful iteration abstractions. Let's continue! 🚀
Enjoying these tutorials?