Objects with metatables
Adding behaviour to a table with a metatable, sharing methods through the index hook, and controlling how an object prints.
A table with rules
A metatable is a second table attached to the first that changes how the first behaves. You attach one with setmetatable, and the fields inside it, each named with two leading underscores, are the hooks Lua looks for.
These hooks are called metamethods. They fire on ordinary operations: reading a key that is not there, joining text, adding numbers. Set one, and the operation quietly does something new.
Sharing methods
The most useful hook is the index hook. When a key is missing from a table, Lua looks it up in the metatable instead. Point that hook at the table itself, and the table can look up its own methods.
This is the usual pattern: a table that holds the methods, a field that points lookups back at it, and a maker that puts that table onto every new object. It is inheritance in three small pieces.
Printing your way
The tostring hook controls what tostring says about your object, and therefore what you see when you print it. Without that hook a table prints as an address, which tells you almost nothing.
A method is just a function that expects the object as its first argument. Writing the definition with a colon quietly adds that first argument, named self, and the call with a colon fills it in.
The lookup chain
Reading a missing key starts a search. Lua looks in the table, then in the metatable's index hook. If that hook is itself a table, the same rule applies to it, so the search can walk a chain of tables and stop at the first value it finds or at nil.
The hook can also be a function. It receives the table and the missing key, and whatever it returns is used as the value. That is the general form; the table form is a shortcut for the common case of sharing methods.
Methods, and the two colons
A method is a function stored in a table that expects the object as its first argument. Writing function Point:show() defines a function whose first parameter is a hidden self; writing p:show() calls it with p in that slot. The two colons are a matched pair, and dropping either shifts every argument by one.
Metamethods are ordinary fields with reserved names. __tostring must return a string, __add receives the two operands, and __eq is consulted when two tables are compared. None of them are called by magic from your code; they fire when the corresponding operator is used.
Writing, and the newindex hook
__index fires on a read of a missing key; __newindex fires on a write to one. If the hook is a table, the assignment goes into that table instead. If it is a function, it receives the table, the key and the value, and decides what to do. That is how a proxy or a read-only table is built.
The hook does not fire when the key already exists in the table. Assigning to an existing field writes it directly, so a validation hook only sees new keys. To guard every write you need an empty proxy table in front of the real data, with __index pointing at the real table and __newindex a function that checks before writing.
Comparison and the other operators
__eq is consulted when two tables are compared with ==, and only when both sides are tables. __lt and __le do the same for < and <=. Defining __eq but not __lt leaves the ordering operators with no rule, which is an error rather than a fallback to identity.
__concat handles .. when one side is your object, __len handles #, and __call lets a table be called like a function. Each hook is just a field with a reserved name; the operator looks it up on the operand and calls it. If the field is missing, Lua raises an error rather than guessing.
A tiny class
local Point = {}
Point.__index = Point
function Point.new(x, y)
return setmetatable({x = x, y = y}, Point)
end
function Point:show()
return "(" .. self.x .. ", " .. self.y .. ")"
end
local p = Point.new(3, 4)
print(p:show())Three object mistakes. Forgetting __index = Class, so the object has its fields but none of the methods, and calling one gives a nil-call error. Returning a number from __tostring, which Lua rejects because printing needs a string. And putting per-object data in the shared class table, where every object overwrites the same field.
Try these next
- Give the Point class a
movemethod that returns a new Point instead of changing the old one. - Add a
__eqhook and compare two points with the same coordinates. - Build a second class whose index hook points at Point, so it inherits Point's methods.
An animal that can speak
Run it and read both lines. Then add a second animal with a different name, add a method of your own, and change the tostring hook so the name comes first.
Vectors that add themselves
Run it and read the three lines. Then add a `__sub` hook and a `length` method that uses the two coordinates.
The colon in the definition and the colon in the call are two halves of the same rule. The definition adds self as a hidden first parameter, and the call fills it with the object on the left. Drop either colon and the arguments shift.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What is a metatable?
