Skip to content
Metatables and __index
step 1/5

Reading — step 1 of 5

Learn

~3 min readTables — 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.colour)

--> 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 defaults = {colour = "black", size = 1}
local item = setmetatable({size = 3}, {__index = defaults})

print(item.size)
print(item.colour)
print(rawget(item, "colour"))

--> 3
--> black
--> nil

Read the third line carefully, because it is the whole idea. item.colour is "black", but item does not have a colour field — rawget looks in the table without consulting the metatable and finds nothing. The lookup only falls through to defaults when the key is genuinely absent, so item.size still answers 3.

__index as a function

If the fallback is a computation rather than a lookup, give __index a function. It 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 function newP(x, y) return setmetatable({x = x, y = y}, P) end

print(tostring(newP(1, 2)))
print(tostring(newP(1, 2) + newP(10, 20)))

--> (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 indexingt.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.

defaults holds colour = "black" and size = "1", and config starts empty with defaults behind it. Read a count, then that many commands:

  • SET key value stores into config itself
  • GET key prints one line

For a GET, print key=value (own) when the key is in config directly, key=value (default) when the value came from defaults, and key=none when neither has it. Telling the first two apart is what rawget is for — plain indexing cannot, because it returns the same value either way.

Discussion

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

Sign in to post a comment or reply.

Loading…

Metatables and __index — Lua Fundamentals