Lua Metatables
Metatable Basics
Setting and using metatables
local t = {}
local mt = {}
setmetatable(t, mt)
-- Check metatable
print(getmetatable(t) == mt) -- true
-- Shorthand: set in constructor
local t2 = setmetatable({}, mt)
Metamethods
__index — property lookup fallback
-- __index as table (prototype chain)
local defaults = {
timeout = 30,
retries = 3,
port = 443,
}
local config = setmetatable({
port = 8080, -- override
}, { __index = defaults })
print(config.port) -- 8080 (found in config)
print(config.timeout) -- 30 (falls through to defaults)
print(config.retries) -- 3 (falls through to defaults)
-- __index as function (computed lookup)
local env = setmetatable({}, {
__index = function(self, key)
return os.getenv(key:upper())
end,
})
print(env.home) -- /home/evanusmodestus (reads $HOME)
__newindex — intercept writes
local readonly = setmetatable({}, {
__newindex = function(self, key, value)
error("attempt to write to readonly table: " .. key)
end,
})
-- readonly.x = 1 -- error!
-- Logging proxy
local tracked = setmetatable({}, {
__newindex = function(self, key, value)
print(string.format("SET %s = %s", key, tostring(value)))
rawset(self, key, value) -- bypass metamethod
end,
})
tracked.hostname = "vault-01" -- prints: SET hostname = vault-01
Operator Overloading
Arithmetic and comparison metamethods
local Vec2 = {}
Vec2.__index = Vec2
function Vec2.new(x, y)
return setmetatable({ x = x, y = y }, Vec2)
end
function Vec2:__add(other)
return Vec2.new(self.x + other.x, self.y + other.y)
end
function Vec2:__eq(other)
return self.x == other.x and self.y == other.y
end
function Vec2:__tostring()
return string.format("(%d, %d)", self.x, self.y)
end
function Vec2:__len()
return math.sqrt(self.x ^ 2 + self.y ^ 2)
end
local a = Vec2.new(1, 2)
local b = Vec2.new(3, 4)
local c = a + b -- Vec2(4, 6) via __add
print(tostring(c)) -- "(4, 6)" via __tostring
print(#a) -- 2.236 via __len
-- Available metamethods:
-- __add(+) __sub(-) __mul(*) __div(/) __mod(%) __pow(^)
-- __unm(unary -) __concat(..) __len(#)
-- __eq(==) __lt(<) __le(<=)
-- __call() __tostring()
OOP with Metatables
Class pattern
local Device = {}
Device.__index = Device
function Device.new(hostname, ip)
return setmetatable({
hostname = hostname,
ip = ip,
}, Device)
end
function Device:ping()
return os.execute("ping -c 1 -W 2 " .. self.ip .. " > /dev/null 2>&1")
end
function Device:__tostring()
return self.hostname .. " (" .. self.ip .. ")"
end
-- Inheritance
local Switch = setmetatable({}, { __index = Device })
Switch.__index = Switch
function Switch.new(hostname, ip, vlans)
local self = Device.new(hostname, ip)
self.vlans = vlans or {}
return setmetatable(self, Switch)
end
function Switch:vlan_count()
return #self.vlans
end
local sw = Switch.new("sw-core-01", "10.50.1.2", { 10, 20, 30 })
print(sw:vlan_count()) -- 3
print(tostring(sw)) -- sw-core-01 (10.50.1.2) -- inherited