What is the best solution? Metatable chain vs. Mix metatable

If you want live delegation (thats where changes in Super automatically visible in Sub + no method copying), use a metatable __index chain. This is standard in Luau.

If you’re going for just snapshot copies (frozen behavior, no lookup hops, manual conflict resolution + higher memory usage) then do mixin/merge, aka shallow copies.

Don’t do long dynamic chains during runtime though, do them once when defining your classes.

For multiple inheritance (whenever its needed), use a function-valued __index that walks a list of supers, or you can pre-flatten into a method table. Don’t try to splice new metatables deep into an existing chain.

Here’s an example of an ‘extender’ function that uses chaining.

local function Extend(Super)
	local Sub = {}
	Sub.__index = Sub
	setmetatable(Sub, {__index = Super})
	return Sub
end
local Animal = {}
Animal.__index = Animal

function Animal.new(name : string)
	local self = setmetatable({}, Animal)
	self.Name = name
	return self
end

function Animal:Speak()
	print(self.Name .. " makes a noise")
end

local Dog = Extend(Animal)

function Dog:Speak()
	print(self.Name .. " barks")
end

and then some other random part in your codebase…

local rover = Dog.new("Rover") -- still calls Animal.new because Dog doesn't override
rover:Speak() -- "Rover barks"

For your actual code in particular, you have some issues though.

Inherit(Sub, Super)

  • This walks chains at runtime then sets new metatables at last.__index or last which is fragile. If you have a metatable already that uses a function __index, your structure is gonezo.

  • Mutating existing class tables after definition is harder to reason from a dev perspective than building a hierarchy.

  • And lastly, if Sub already has an __index pointing to itself (which is normal), chaining via that last.__index or last may attach below the wrong node depending on what layout you use. Not a good general application function.

Mix(Table, Meta)

  • If the table already has a metatable, you’ll be overwriting keys in its metatable, not its class table, which… like, you never really wanna do that ever.

  • Merge(Meta, last) copies meta keys into the existing meta, not into the class.

  • This is small but you don’t need to use pairs() in Luau. It’s built into the syntax; the interpreter decides the best iterator to use when parsing.

Consider doing something more functional, like imperative inheritance abstraction:

local function ApplyMixin(Target, Mixin)
	for k, v in Mixin do
		if Target[k] == nil then
			Target[k] = v
-- missing some error handling but yeah
		end
	end
end

local function Extend(Super)
	-- Sub "class" table
	local Sub = {}
	Sub.__index = Sub

	-- inheritance: Sub inherits methods from Super
	setmetatable(Sub, {__index = Super})

	return Sub
end

local Base = {}
Base.__index = Base

local Derived = Extend(Base)
ApplyMixin(Derived, Serializable) -- example method
ApplyMixin(Derived, Observable) -- other example method

And this is all noting that really inheritance is mostly useless in Roblox Luau anyway. It’s not really an OOP language. General functional programming with occasional OOP ideology will serve you best. It’s also far more readable and understandable. There’s a reason you’re getting little replies. Keeping it simple like this is what’ll work best in the long term.

1 Like