Skip to content
AgileLogics
5 min read

Inheritance

Prerequisites: Object-oriented programming

Inheritance is a chain of __index lookups: an instance looks in its class, the class looks in its parent class.

local Shape = {}
Shape.__index = Shape

function Shape.new(name)
  return setmetatable({name = name}, Shape)
end
function Shape:area() return 0 end
function Shape:describe()
  return string.format("%s with area %.2f", self.name, self:area())
end

local Circle = setmetatable({}, {__index = Shape})
Circle.__index = Circle

function Circle.new(radius)
  local self = Shape.new("Circle")
  self.radius = radius
  return setmetatable(self, Circle)
end
function Circle:area() return math.pi * self.radius ^ 2 end

local Square = setmetatable({}, {__index = Shape})
Square.__index = Square
function Square.new(side)
  local self = setmetatable(Shape.new("Square"), Square)
  self.side = side
  return self
end
function Square:area() return self.side * self.side end

for _, s in ipairs({Circle.new(1), Square.new(3), Shape.new("Blob")}) do
  print(s:describe())
end
Circle with area 3.14
Square with area 9.00
Blob with area 0.00
  • setmetatable(Circle, {__index = Shape}) makes Circle fall back to Shape for methods it doesn't define, like describe.
  • The child constructor calls the parent's constructor, then switches the metatable to the child class.
  • self:area() in describe calls the child's area — that's polymorphism.

A small class helper

Projects usually wrap this pattern in a helper (or use a library like middleclass or classic):

local function class(parent)
  local c = {}
  c.__index = c
  if parent then setmetatable(c, {__index = parent}) end
  c.new = function(...)
    local obj = setmetatable({}, c)
    if obj.init then obj:init(...) end
    return obj
  end
  return c
end

local Animal = class()
function Animal:init(name) self.name = name end
function Animal:speak() return self.name .. " makes a sound" end

local Dog = class(Animal)
function Dog:speak() return self.name .. " barks" end

print(Animal.new("Generic"):speak())
print(Dog.new("Rex"):speak())
Generic makes a sound
Rex barks

Dog inherits init from Animal and overrides speak. To call the parent's version from a child, call it explicitly: Animal.speak(self).