Single and Multiple Inheritance in Lua
Lua doesn’t have built-in class-based inheritance. Instead, it uses metatables — a powerful mechanism that lets objects delegate property lookups to other tables. This approach is flexible enough to support both single inheritance and the more complex multiple inheritance pattern.
In this tutorial, you’ll learn how to implement both inheritance styles, understand how method lookup works, and avoid common pitfalls.
Understanding Metatables and __index
Before diving into inheritance, you need to understand how metatables work in Lua. A metatable is a table that defines custom behavior for another table. The __index metamethod is the key to inheritance — it’s called when you access a key that doesn’t exist in a table.
local parent = {
name = "Parent",
greet = function(self)
return "Hello, I'm " .. self.name
end
}
local child = {}
setmetatable(child, { __index = parent })
print(child.name) -- Output: Parent
print(child:greet()) -- Output: Hello, I'm Parent
Notice that child has no name property of its own, yet accessing child.name returns "Parent". The __index metamethod intercepts the lookup and finds the value in parent.
Single Inheritance with Metatables
Single inheritance is straightforward: each subclass has exactly one parent. The pattern involves creating a constructor that sets up the metatable chain.
-- Base Animal class
local Animal = {}
Animal.__index = Animal
function Animal:new(name, sound)
local instance = setmetatable({}, self)
instance.name = name
instance.sound = sound
return instance
end
function Animal:speak()
return self.name .. " says " .. self.sound
end
-- Dog subclass
local Dog = setmetatable({}, { __index = Animal })
Dog.__index = Dog
function Dog:new(name)
local instance = setmetatable({}, self)
instance.name = name
instance.sound = "woof"
return instance
end
-- Create instances
local generic = Animal:new("Creature", "grrr")
local buddy = Dog:new("Buddy")
print(generic:speak()) -- Output: Creature says grrr
print(buddy:speak()) -- Output: Buddy says woof
The key is setmetatable(Dog, { __index = Animal }). When buddy:speak() is called, Lua first looks in the Dog table, doesn’t find speak, then checks the metatable’s __index and finds it in Animal.
Adding Methods to Subclasses
You can override methods in the subclass:
function Dog:speak()
return self.name .. " barks: " .. self.sound
end
print(buddy:speak()) -- Output: Buddy barks: woof
The method lookup finds speak in Dog first, so the subclass version is used. This is the foundation of polymorphism in Lua.
Calling Super Methods
When you override a method but need to call the parent’s version, you reference it directly:
local Cat = setmetatable({}, { __index = Animal })
Cat.__index = Cat
function Cat:new(name)
local instance = setmetatable({}, self)
instance.name = name
instance.sound = "meow"
return instance
end
function Cat:speak()
-- Call the parent method first
local parentSpeech = Animal.speak(self)
return parentSpeech .. " and " .. self.sound .. "!"
end
local whiskers = Cat:new("Whiskers")
print(whiskers:speak())
-- Output: Whiskers says meow and meow!
Notice the syntax: Parent.method(self) passes the instance explicitly. This is different from self:method() which would look in the current class first.
Multiple Inheritance
Multiple inheritance allows a class to inherit from more than one parent. This is more complex because the __index metamethod needs to search multiple tables.
-- Two parent classes
local Flyer = {
fly = function(self)
return self.name .. " is flying"
end
}
local Swimmer = {
swim = function(self)
return self.name .. " is swimming"
end
}
-- Duck inherits from both
local Duck = setmetatable({}, {
__index = function(self, key)
-- Search Flyer first
if Flyer[key] then
return Flyer[key]
end
-- Then search Swimmer
if Swimmer[key] then
return Swimmer[key]
end
return nil
end
})
function Duck:new(name)
local instance = setmetatable({}, { __index = getmetatable(self).__index })
instance.name = name
return instance
end
local donald = Duck:new("Donald")
print(donald:fly()) -- Output: Donald is flying
print(donald:swim()) -- Output: Donald is swimming
The custom __index function searches each parent table in order. You can extend this pattern to handle any number of parents.
A Reusable Multiple Inheritance Helper
For production code, create a helper function:
function createClass(...)
local parents = {...}
local class = {}
setmetatable(class, {
__index = function(self, key)
for _, parent in ipairs(parents) do
if parent[key] then
return parent[key]
end
end
return nil
end
})
return class
end
-- Now easy multiple inheritance
local Amphibian = createClass(Flyer, Swimmer)
function Amphibian:new(name)
return setmetatable({ name = name }, { __index = self })
end
Common Gotchas
Method Lookup Order
The lookup order matters more than you might expect. Lua searches in this sequence:
- The object itself
- The object’s metatable’s
__index(which may be the class table) - If
__indexis a function, it runs and may delegate to other tables
This means if two parents define the same method, the first one in your search order wins.
Private Fields Don’t Exist
Lua has no true private fields. By convention, prefix with underscore to indicate privacy, but it’s purely social:
function Account:new(balance)
local instance = setmetatable({}, self)
instance._balance = balance -- "private" by convention
return instance
end
local acc = Account:new(100)
print(acc._balance) -- Still accessible! No true privacy
Super Calls Are Manual
Unlike languages with explicit super keywords, Lua requires you to call parent methods directly:
function SubClass:method()
ParentClass.method(self) -- Must explicitly name the parent
end
If you rename a parent class, you’ll need to update all super calls manually.
Self Reference Gotcha
When calling parent methods, always pass self:
-- Correct
function Dog:new(name)
local instance = Animal.new(self, name) -- Pass self as first arg
instance.sound = "woof"
return instance
end
Metatable Chain Gotcha
Be careful when mixing setmetatable calls. Each class needs its own metatable setup:
-- Wrong: all classes share the same metatable
local Class2 = {}
Class2.__index = Class2
setmetatable(Class2, { __index = Class1 }) -- Modifies Class2's metatable
-- Correct: instance gets the class, class gets its parent
local instance = setmetatable({}, Class2)
Summary
Lua’s inheritance system is built on metatables and the __index metamethod. Single inheritance is simple: set your subclass’s metatable to point to the parent with __index = parent. Multiple inheritance requires a custom __index function that searches multiple parent tables.
Key takeaways:
- Use
setmetatable(subclass, { __index = parent })for single inheritance - Implement a search function in
__indexfor multiple inheritance - Call parent methods explicitly:
Parent.method(self) - Remember there’s no true privacy — use conventions, not language features
- Understand method lookup order to predict which method gets called
With these patterns, you can build class hierarchies in Lua that rival those of traditional OOP languages.