Skip to content
Lesson 12 of 18

Step 1 of 5 · Reading · ~3 min

Learn

Tables — The One Data Structure

Metatables and __index

Every table you have written so far answers a missing key the same way:

local t = {size = 3}
print(t.weight)

--> nil

A metatable lets you change that answer. It is an ordinary table whose keys have special names, attached with setmetatable, and it is the mechanism behind inheritance, operator overloading and every Lua "class" you will ever read.

__index as a fallback table

local base = {font = "serif", points = 12}
local heading = setmetatable({points = 20}, {__index = base})

print(heading.points)
print(heading.font)
print(rawget(heading, "font"))

--> 20
--> serif
--> nil

Read the third line carefully, because it is the whole idea. heading.font is "serif", but heading does not have a font field: rawget looks in the table without consulting the metatable and finds nothing. The lookup only falls through to base when the key is genuinely absent, so heading.points still answers 20.

Writes are different. heading.font = "mono" stores into heading itself and leaves base alone, because __index is consulted for reading only. After that write, rawget(heading, "font") answers "mono" as well.

__index as a function

If the fallback is a computation rather than a lookup, give __index a function. This is the first place the course writes one, so here is all you need: function(a, b) ... end is a block of code stored like any other value, and it runs when something calls it with arguments; return hands a result back. The Defining Functions lesson covers parameters and results in full. Here the function receives the table and the missing key:

local squares = setmetatable({}, {__index = function(_, k) return k * k end})
print(squares[7], squares[9])

--> 49	81

Operators

The same trick names the arithmetic and comparison hooks. __tostring is the one you will reach for first, because it fixes print:

local P = {}
P.__index = P
P.__tostring = function(p) return "(" .. p.x .. "," .. p.y .. ")" end
P.__add = function(a, b) return setmetatable({x = a.x + b.x, y = a.y + b.y}, P) end

local a = setmetatable({x = 1, y = 2}, P)
local b = setmetatable({x = 10, y = 20}, P)

print(tostring(a))
print(tostring(a + b))

--> (1,2)
--> (11,22)

__eq, __lt, __len and __call work the same way — __len is what # consults, and __call makes a table usable as a function.

Notice P.__index = P. That line is the class pattern: the metatable points at itself, so any method stored on P is found by every table created with it as a metatable. The next lesson builds on exactly that.

Common mistakes

  • Expecting __index to fire for a key that exists — it is consulted only when the key is absent, which is why an own field always wins.
  • Confusing rawget with normal indexing — t.k may come from the metatable; rawget(t, "k") never does. Use it when you need to know which.
  • Forgetting setmetatable returns the table — that is why return setmetatable({...}, P) is the idiom rather than three lines.
  • Setting __index to a function that forgets to return — the caller then sees nil and the fallback looks broken.
  • Assuming metatables are inherited automatically — each table needs its own setmetatable call; the constructor is what makes that reliable.

Your exercise

Config With Defaults builds the fallback pattern and then reports which side answered.

The starter gives you a defaults table and a command loop. You create config so that it starts empty with defaults behind it, then handle two commands: one stores into config itself, the other looks a key up and reports whether the answer came from config or from defaults. Plain indexing cannot tell those two apart, because it returns the same value either way, and telling them apart is what rawget is for.

The mistakes the grader will catch: storing into defaults instead of config changes the answer for every later lookup; and reporting by checking config[key] alone labels every default as "own".

Up nextObjects: the colon, self, and the class patternTables — The One Data Structure

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…