Why Metatables/Metamethods?

I understand what metatables and metamethods are and how they’re used, but what I don’t understand is why you would use them over something simpler.

A use case I thought of was an admin command handler with an array of valid commands. If the table is indexed and the requested command is not found, an __index metamethod could catch this and display an error to the user.

E.g.,

local Commands =  {"help", "cmds", "kick", "ban"}
local TestCommand = "test123" -- Player.Chatted value
local Catch = {__index = function()
return TestCommand.." is not a valid command!"
end}

setmetatable(Commands, Catch)
print(Commands[TestCommand])

But couldn’t the same thing be achieved by:

local Commands =  {"help", "cmds", "kick", "ban"}
local TestCommand = "test123"

if not Commands[TestCommand] then
print(TestCommand.." is not a valid command!"
end

Even if the first example was more performant, I’d argue the second example is much more readable, and the slight gain in performance wouldn’t justify using a metatable. So are there any use cases where:

  • You’re likely to run into it (i.e., not an obscure example)
  • A metatable is substantially more performant/readable

https://www.lua.org/pil/13.html
https://www.lua.org/pil/13.1.html
https://www.lua.org/pil/13.2.html
https://www.lua.org/pil/13.3.html
https://www.lua.org/pil/13.4.html
https://www.lua.org/pil/13.4.1.html
https://www.lua.org/pil/13.4.2.html
https://www.lua.org/pil/13.4.3.html
https://www.lua.org/pil/13.4.4.html
https://www.lua.org/pil/13.4.5.html

The __newindex and __index metamethods of metatables, allow you to setup different behaviors to enact when an attempt is made to either create a new key/field within a table of which the metatable is set for or when an attempt is made to access the value of a key/field which is not present within a table of which the metatable is set for.

1 Like

I am fully aware of what they are. That is not what I am asking. I am asking for use cases where not using a metatable would result in significantly worse performing code.

I wrote a short summary about __newindex and __index below all the links. Those 2 are probably the one’s most frequently used, and they allow for you to setup defaults in tables (as opposed to the standard default which is nil).

The use case you will most often run into is OOP (object oriented programming)

local object = {}
object.__index = {} -- I prefer to seperate out my index table, but some will just have it refer back to object
function object.new()
    local self = {} 
    return setmetatable(self, object)
end
function object.__index:epicGamerFunction()

end

But why not do it this way?

local object = {}
function object.new()
    local self = {} 
    function self:epicGamerFunction()

    end
    return self
end

This was less memory efficient, since every time you created a new object, a new function was created. (I’m not sure if this is a case do to a recent update). I would argue that the metatable method is more readable, since you aren’t cramming it all into a function and the indentation it brings with it, and I believe it’s more performant. It also allows for inheritance, but that’s outside the scope of this reply.

5 Likes

Interesting. I myself am still learning the basics of OOP, but this seems like a good use case. Just out of curiosity, are there any non-OOP use cases?

OOP is seldom used in Lua in general and even less so in Roblox. Considering Lua isn’t an OOP language.

1 Like

Yes…

I even used __index in my examples. I’m not asking for what they do, I’m asking for methods in which their usage is better than a non-metatable alternative.

Here’s a more extensive thread, the search tool is a neat feature, give it a go!

1 Like

That’s not true, I use it a lot, and there are many libraries that make use of it. Obviously, it is just a tool, so don’t overuse it.

These 2 was implemented in OOP, but I think it’s still worth mentioning.

  • “Event” tables using __newindex
  • A Vector4 implementation using __add, __sub, etc.
1 Like

Just because you frequent OOP concepts in Lua doesn’t mean it’s not seldom used. You likely account for a very small minority. I’m fairly sure the Lua documentation even states that due to the nature in which tables work OOP is for the most part, essentially made redundant. Anyway, I don’t want to derail this thread so let’s just continue providing usecases for the original poster.

1 Like

With inheritances you can create objects, and thus control your methods in a more readable, aesthetic and versatile way.

local MyClass = {};
MyClass.__index = MyClass

setmetatable(MyClass, {
	__call = function(class, ...)
		return class._new(...)
	end,
})

function MyClass._new(init)
	local self = setmetatable({}, MyClass)
	self.value = init
	return self
end

function MyClass:SetValue(x)
	self.value = x
end

function MyClass:GetValue()
	return self.value
end

-- You can create Objects

local Object = MyClass(5)

print( Object:GetValue() )
Object:SetValue(10)
print( Object:GetValue() )
1 Like

What you’ll actually find is that a metatable has the exact same creation cost as a normal table because that’s all that it is.

I don’t see much use for metatables in admin commands, because commands are explicit to existing, there would be no need for the linkage between tables.

I’ll give you some more examples to broaden your horizons some. I would argue that the most common use of metatables across all of Luau is custom libraries / custom objects / OOP. If you’re going to learn to write reasonable inheritance, then you can and should be using metatables. Here’s an example.

local Class = {}
Class.__index = Class

function Class.new(t)
    setmetatable(t, Class)
    return t
end

This is an incredibly basic constructor used to build a custom class. We can write Variable = Class.new{} and instantly have a table that exists under this new bracket. Calling an empty function inside of this new variable will immediately search inside of Class.

Here’s why it matters:

function Class:Clear()
	return Class.new{}
end

local Example = Class.new{}

Without an attached metatable with an __index metamethod, we cannot write Example:Clear() unless that function is wrote explicitly inside of the table provided by our variable. Writing to a metatable across numerous objects saves you memory by allowing you to direct functions from inside of your class specifically. It also allows you to write default values for your class. Let’s look at that next. We’ll start by writing an empty property inside of our class.

Class.Defaults = {x = 100, y = 100, z = 100}

function Class.new(t, ...)
    local new = t
    new.Values = {}
    setmetatable(new, Class)
    setmetatable(new.Values, {__index = Class.Defaults})
    
    return new
end

Next, we’ll write a function to increment our object even though we never explicitly gave it any values (however by adding … to our function parameters we could catch values in the future. This is how classes work in nature. Whenever you write Vector3.new() without passing arguments you are indexing the vector metatable’s value defaults. You can prove this to yourself by writing print(getmetatable(Vector3.new())), which will provide that the metatable is locked because it holds the __metatable metamethod.

My point in saying that is because Roblox up until and including where it is wrote in C, is wrote into classes. That is how and why inheritance works. Roblox is a hierarchy.

Looking back at our example, we can now write a function to increment our class about an axis.

function Class:Increment(byAxis)
    self.Values[byAxis] += 1
end

Example:Increment("x")
print(Example.Values.x) -- 101
Full example for further practice (click to open)
local Class = {}
Class.Defaults = {x = 100, y = 100, z = 100}

Class.__index = Class

function Class.new(t, ...)
	local new = t
	new.Values = {}
	setmetatable(new, Class)
	setmetatable(new.Values, {__index = Class.Defaults})

	return new
end

function Class:Clear()
	return Class.new{}
end

function Class:Increment(byAxis)
	self.Values[byAxis] += 1
end

local Example = Class.new{}

Example:Increment("x")
print(Example.Values.x) -- 101

Example = Example:Clear()
print(Example.Values.x) -- 100

Metatables are worth fully understanding and doing research on, because using them results in incredibly powerful structures with practice.

There are some notable differences between vanilla Lua metatables and Luau metatables.

  • __gc (fires when table is garbage collected) does not work with Luau.
  • References to Roblox instances are never weak – this is to say that there is differences in __mode performance.

Some other reasonable uses for metatables in Lua are: table math for greater readability, writing immutable tables without using new table.freeze() method, building a “Changed” event for tables by using a proxy table (similar to read-only).

Hopefully this helps you learn some, if you have any questions feel free to ask me. Metatables are absolutely worth using and learning. Anyone who tells you otherwise is speaking from a lack of experience.


You are incorrect. OOP is incredibly common within well made games on Roblox, why would it not be with the amount of benefits that it holds.

Any kind of source for this at all? Lua.org fully supports OOP regardless of the language’s nature, in fact the Lua guidebook has an entire chapter on it. Don’t make claims like this without a source.

Lua not being an OOP language should be a fairly reliable source. I’ll see what I can find though, since it has been a while.

just to support this comment. This is a use case for metatables
Imagine this

MyClass.New = function()
	self = {}

	function self:Function()
		-- some function
	end

	return self
end

you could do that, one without the use of metatable, however that creates a new function for each time you call MyClass.new, using so many memory. When using metamethods instead

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

function MyClass:Function()
	-- some function
end

all objects have the function MyClass:Function, but it was only created once and is used by all objects which saves up more memory than the other one

The Lua style guide provides some input on the use of metatables and how the Roblox team themselves use them. The section on preventing typos is a particularly good none OOP example.

https://roblox.github.io/lua-style-guide/

As far as common usage of OOP, I have read in several articles that the Roblox API is OOP based, which makes sense since it is a wrapper over C++ libraries.

there is no simple performance reason to use a metatable.
sometimes even it creates negligible overhead,
and sometimes solves one (garbage collection magic)

however if you try it yourself you’ll see it opens your code up to lots of flexible behaviours which makes it useful for distributions such as libraries/custom classes/etc
individual use of object oriented setups, and following the single responsibility principle is so that code can easily adopt design patterns with minimal refactoring. its greatest efficiency is for design of future systems

This is actually a lie, and a mistake by Roblox’s Dev Hub page.
Instances aren’t weak.
As long as they aren’t in the DataModel tree, and they don’t have any strong references in code, they will be garbage collected.
It’s also good to note this topic to understand what counts as “strong reference” when working with Instances.

As for it having a different performance than other objects in weak tables, I believe that’s not the case.