Reading — step 1 of 5
Learn
Objects: the colon, self, and the class pattern
Metatables and __index finished on a line it did not explain: P.__index = P. That assignment is how every Lua class is built, and the colon you have been typing since String Basics — s:upper() — is the other half of the trick. Lua has no class keyword. It has these two things, and they are enough.
The colon passes one extra argument
t:m(x) means t.m(t, x). The table on the left is handed to the function as its first argument, and that is the whole feature.
local counter = {n = 0}
function counter.bump(self, by)
self.n = self.n + by
end
counter.bump(counter, 5) -- spelled out
counter:bump(3) -- the colon passes counter for you
print(counter.n)
--> 8
The definition side has the matching shorthand. function counter:bump(by) silently declares a parameter named self ahead of the ones you wrote, so it is the same function as the function counter.bump(self, by) above — one spells self out, the other does not.
self is not a keyword either. Name that first parameter this and everything still works; self is simply the name the colon form generates, and every Lua programmer expects to see it.
A class is a table that is its own fallback
local Account = {}
Account.__index = Account
function Account.new(owner)
return setmetatable({owner = owner, balance = 0}, Account)
end
function Account:deposit(amount)
self.balance = self.balance + amount
return self.balance
end
local a = Account.new("ada")
print(a:deposit(50))
print(a:deposit(25))
print(rawget(a, "deposit"), type(a.deposit))
--> 50
--> 75
--> nil function
Account.__index = Account makes the class its own fallback, so a lookup that misses on the instance finds the method on the class. new uses a dot because there is no instance yet — it is the thing that makes one; deposit uses a colon because there is. The last printed line is the proof: a has no deposit field of its own, and the call reached it through the metatable exactly as item.colour reached defaults. Instances hold data, the class holds behaviour, __index is the wire.
The mistake everybody makes once
Call a method with a dot and nothing is passed for self, so your first argument lands there instead. One more line on the end of that same file:
print(pcall(function() return a.deposit(5) end))
--> false script.lua:9: attempt to index a number value (local 'self')
Line 9 is self.balance = self.balance + amount, up in the class: 5 became self, and the body tried to index a number. Read that message as "you wrote . where you meant :" — it is almost never anything else.
Never put a mutable value on the class
The class table is shared by every instance, so a table stored there is one table, not one per object.
local Bag = {}
Bag.__index = Bag
Bag.items = {} -- one list, shared by every bag
function Bag.new() return setmetatable({}, Bag) end
function Bag:add(v) self.items[#self.items + 1] = v end
local x, y = Bag.new(), Bag.new()
x:add("apple")
y:add("pear")
print(#x.items, #y.items)
--> 2 2
self.items missed on both instances and fell through to Bag.items — the same table each time. Build it inside the constructor instead, setmetatable({items = {}}, Bag), and the identical program prints 1 1. A number on the class is harmless — self.n = self.n + 1 reads the class value and writes a new field on the instance — so it is shared mutable values that bite.
Common mistakes
- Calling with
.—a.deposit(5)passes nothing forself, so5becomes the receiver and the body dies withattempt to index a number value (local 'self'). - Forgetting
Class.__index = Class— instances are then plain tables, and the first method call fails withattempt to call a nil value (method 'deposit'). - A constructor defined with a colon —
function Account:new(owner)called asAccount.new("ada")puts"ada"intoselfand leavesownernil. No error; just an object with no owner. - A table field on the class — every instance shares it. Create per-instance tables inside the constructor.
- Detaching a method —
local d = a.depositthend(10)drops the receiver and fails like a dot call.
Your exercise
Two Stacks hands you two independent stacks, a and b, and a stream of commands. You write the Stack class: the constructor plus push, pop and size.
Two mistakes the grader will catch, both from this page. Define the methods with a colon so they receive self — a function Stack.push(v) that the driver invokes as s:push(value) binds the stack to v and loses the value entirely. And create items inside Stack.new rather than as Stack.items = {} on the class: that version passes the single-stack test, then reports y when you pop a, because both stacks have been appending to one list.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…