0Pricing
Lua Academy · Lesson

Introduction to Metatables

Set and get metatables with setmetatable/getmetatable.

Introduction to Metatables is a free Lua Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Lua Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

What is a Metatable?

A metatable is a regular Lua table that controls the behavior of another table. When Lua encounters an operation it can't perform on a table (like arithmetic or indexing a missing key), it looks for a corresponding metamethod in the metatable. This is Lua's extension mechanism for operator overloading and OOP.

local t = {}
local mt = {__index = function(_, k) return "default_" .. k end}

setmetatable(t, mt)

print(t.foo)    -- default_foo
print(t.bar)    -- default_bar
print(t[1])     -- default_1

setmetatable and getmetatable

setmetatable(t, mt) sets the metatable of t to mt and returns t. getmetatable(t) returns the metatable, or nil if none. You can protect a metatable from being changed by setting mt.__metatable = "protected".

local t = {x = 10}
local mt = {}

setmetatable(t, mt)
print(getmetatable(t) == mt)   -- true

-- Protect metatable
mt.__metatable = "locked"
print(getmetatable(t))   -- locked
-- setmetatable(t, {})  -- ERROR: cannot change protected metatable

__index as Table

When __index is a table, Lua looks up the missing key in that table. This is the foundation of Lua's prototype inheritance. If the key is not in __index either, Lua checks its metatable, and so on up the chain.

local Animal = {sound = "...", legs = 4}

local Dog = setmetatable({sound = "Woof"}, {__index = Animal})
local Cat = setmetatable({sound = "Meow"}, {__index = Animal})

print(Dog.sound)   -- Woof (own field)
print(Dog.legs)    -- 4 (from Animal)
print(Cat.sound)   -- Meow
print(Cat.legs)    -- 4

__index as Function

When __index is a function, Lua calls it with the table and the missing key. The function can compute or fetch the value dynamically. Return nil from __index to indicate "key not found."

local dynamic = setmetatable({}, {
  __index = function(t, k)
    if k:sub(1,4) == "get_" then
      local field = k:sub(5)
      return function(self) return self[field] end
    end
    return nil
  end
})

dynamic.name = "Lua"
local getter = dynamic.get_name
print(getter(dynamic))   -- Lua

rawget and rawset

rawget(t, k) reads a table key without triggering __index. rawset(t, k, v) sets a key without triggering __newindex. Use these to bypass metamethods when you need direct table access inside a metamethod to avoid infinite recursion.

local logged = setmetatable({}, {
  __newindex = function(t, k, v)
    print("Setting", k, "=", v)
    rawset(t, k, v)  -- bypass __newindex
  end
})

logged.x = 10   -- Setting x = 10
logged.x = 20   -- no log! key now exists in t
print(logged.x) -- 20

Multiple Tables Sharing a Metatable

A single metatable can be shared by many tables. This is how Lua class systems work: all instances of a class share the same metatable, which holds the methods. Changes to the metatable affect all instances.

local Mt = {
  __index = {
    greet = function(self)
      print("Hello from " .. (self.name or "?"))
    end
  }
}

local a = setmetatable({name="Alice"}, Mt)
local b = setmetatable({name="Bob"},   Mt)

a:greet()   -- Hello from Alice
b:greet()   -- Hello from Bob

__metatable Field

Setting mt.__metatable to any value causes getmetatable to return that value instead of the real metatable. This prevents users from inspecting or replacing the metatable — useful for encapsulation and security.

local secret = setmetatable({}, {
  __metatable = false,    -- hide metatable
  __index = {value = 42}
})

print(secret.value)        -- 42
print(getmetatable(secret)) -- false (not the real mt)
-- setmetatable(secret, {}) -- ERROR

Checking for Metatable

Use getmetatable(t) to check if an object has a specific metatable (for type checking). Since __metatable can mask the real metatable, use rawequal or a marker field for reliable isinstance checks.

local MyClass = {}
MyClass.__index = MyClass
MyClass.__metatable = MyClass   -- use self as marker

function MyClass.new(x)
  return setmetatable({x=x}, MyClass)
end

local obj = MyClass.new(5)
print(getmetatable(obj) == MyClass)   -- true
print(obj.x)                           -- 5

Metatables on Non-Tables

Strings in Lua already have a metatable (the string module). This is why you can call s:upper() on any string. You cannot set metatables on strings, numbers, or booleans from pure Lua — only on tables and userdata.

local s = "hello"
local mt = getmetatable(s)

-- String metatable has __index = string library
print(mt.__index == string)   -- true

-- So all string functions are methods:
print(s:upper())              -- HELLO
print(s:rep(3, "-"))          -- hello-hello-hello

Debugging with Metatables

You can add a __tostring metamethod to control how tostring() and print() display a table. This is invaluable for debugging complex objects.

local Point = {}
Point.__index = Point
Point.__tostring = function(p)
  return string.format("Point(%g, %g)", p.x, p.y)
end

function Point.new(x, y)
  return setmetatable({x=x,y=y}, Point)
end

local p = Point.new(3, 4)
print(tostring(p))   -- Point(3, 4)
print(p)             -- Point(3, 4)  (print calls tostring)

Quick Check

What happens when you access a key not found in a table that has a metatable with __index set to another table?

Recap: Metatables

Summary:

  • setmetatable(t, mt) / getmetatable(t)
  • __index: table → prototype lookup; function → dynamic dispatch
  • rawget/rawset bypass metamethods
  • Shared metatables enable class-like behavior
  • __metatable protects the metatable from inspection
  • __tostring controls print output

Frequently asked questions

Is the “Introduction to Metatables” lesson free?

Yes — the full text of “Introduction to Metatables” is free to read here on the web, and the Lua Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Lua Academy course, upgrade to CoddyKit PRO.

What will I learn in “Introduction to Metatables”?

Set and get metatables with setmetatable/getmetatable. You practise Lua Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Lua Academy?

No prior experience is required. Lua Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Introduction to Metatables” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Lua Academy lesson?

Yes. Every Lua Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Introduction to Metatables
  2. __index and __newindex
  3. Arithmetic Metamethods
  4. __tostring and __len
← Back to Lua Academy