Object-Oriented Programming
Prerequisites: __index and __newindex
Lua has no class keyword. Classes are built from tables and
metatables — a pattern so common it's worth knowing by heart.
local Account = {}
Account.__index = Account
function Account.new(owner, balance)
local self = setmetatable({}, Account)
self.owner = owner
self.balance = balance or 0
return self
end
function Account:deposit(amount)
assert(amount > 0, "deposit must be positive")
self.balance = self.balance + amount
end
function Account:report()
return self.owner .. ": " .. self.balance
end
local acc = Account.new("Ada", 100)
acc:deposit(50)
print(acc:report())
print(acc.balance)
local other = Account.new("Linus")
print(other:report())
Ada: 150
150
Linus: 0
How it works:
Accountholds the methods;Account.__index = Accountmeans "instances look up missing keys (their methods) inAccount".- Each instance is a small table with its own fields (
owner,balance), whose metatable isAccount. function Account:deposit(amount)is shorthand forfunction Account.deposit(self, amount).acc:deposit(50)is shorthand foracc.deposit(acc, 50)— the colon passes the object asself.
The colon mistake
local Counter = {}
Counter.__index = Counter
function Counter.new() return setmetatable({n = 0}, Counter) end
function Counter:increment() self.n = self.n + 1 end
local c = Counter.new()
c:increment()
print(c.n)
print(pcall(c.increment))
1
false main.lua:4: attempt to index a nil value (local 'self')
Calling with a dot (c.increment()) passes no self, so self is nil.
When you see "attempt to index a nil value (local 'self')", look for a
. that should be a :.
Private state with closures
For true privacy, build objects from closures instead of metatables:
local function newWallet(start)
local balance = start
return {
spend = function(amount)
if amount > balance then return false end
balance = balance - amount
return true
end,
balance = function() return balance end,
}
end
local w = newWallet(20)
print(w.spend(15), w.spend(10), w.balance())
true false 5
Closure-based objects hide their state completely, but each instance carries its own copy of every method, so they use more memory than the metatable pattern.