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

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve? Keep it simple and clear!

  2. What is the issue? Include screenshots / videos if possible!

  3. What solutions have you tried so far? Did you look for solutions on the Creator Hub?

After that, you should include more details if you have any. Try to make your topic as descriptive as possible, so that it’s easier for people to help you!

function Inherit(Sub, Super)
	local temp = {__index = Super}
	local last = Sub
	local currentMetatable = getmetatable(Sub) -- First call outside the loop

	while currentMetatable do -- Check the stored value
		last = currentMetatable -- Assign the stored value
		currentMetatable = getmetatable(last) -- Get the *next* value for the next iteration
	end

	setmetatable(last.__index or last, temp)
end

local function Merge(A, B)
	for k, v in pairs(A) do
		B[k] = v
	end
end

function Mix(Table, Meta)
	local last = Table
	local currentMetatable = getmetatable(Table) -- First call outside the loop

	while currentMetatable do -- Check the stored value
		last = currentMetatable -- Assign the stored value
		currentMetatable = getmetatable(last) -- Get the *next* value for the next iteration
	end

	if last == Table then
		setmetatable(Table, Meta)
	else
		Merge(Meta, last)
	end
end

What is the best solution to inheritance?

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

what should I do if I need a sub class to inherit from multiple super classes?

ah so multiple inheritance, you’d do a search of an order list of supers, heres an example based off of that Extend

local function ExtendMultiple(...)
    local Supers = {...}  

    local Sub = {}   
    Sub.__index = Sub 

    setmetatable(Sub, { 
        __index = function(_, key) 
            for i = 1, #Supers do
                local value = Supers[i][key]
                if value ~= nil then
                    return value  -- first hit wins
                end
            end
            return nil
        end,
    })

    return Sub
end

However!

If your supers aren’t gonna change at runtime, you can just use some mixins

local function ApplyMixin(Target, Source)
    for key, value in Source do
        if Target[key] == nil then
            Target[key] = value
        end
    end
end

local Plane = {} -- normal single‑inherit class
Plane.__index = Plane

ApplyMixin(Plane, Flyable)
ApplyMixin(Plane, Shootable) -- just some example i made up

edit: removed next() lol old habit

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.