Welcome, I’ll be referencing this post a lot in posts with people who are confused about metatables.
Mega Revision (January 24, 2024)
Prerequisite (stuff you need to know before reading) knowledge needed:
- Good understanding of Roblox tables and the
table
class methods (i.e. table.insert
) although I don’t use or mention these (it’s just good to understand these)
- Good understanding of functions
- Good understanding of basically well, a lot of other things
I’m not sure if I even have the right idea but I’ll try to explain them in the simplest way I can:
Metatables like many say are “more powerful tables”. And they are, they have metamethods; these are what give the tables more functionality!
Before I go over the metamethods, lemme explain the setmetatable
function.
So, the setmetatable
function is used to well, create a metatable. This function takes two arguments, a table
“the child table” and another table which will be the metatable
“the parent table”.
Now, you can set the setmetatable
function to a variable (which will return the metatable table) like so:
local Mt = setmetatable({}, {
__index = function(t, k)
return t[k]
end
})
--[[ returns the table
with the __index
function to the
variable “Mt”
]]
You probably see the __index
metamethod, the most common one. In the most simple way I can think of explaining it, the __index
metamethod fires whenever you try to get a nil table index (basically a value that doesn’t exist) from your child table or metatable (someone might be able to correct me on this). Here’s a little example:
local Example = setmetatable({
["happyValue"] = "Happy!"
}, {
__index = function(t, k)
print("Fired!")
t[k] = k
return k
end
})
print(Example["happyValue"]) -> "Happy!"
print(Example["sadValue"]) -> nil
-> from the __index function : "Fired!"
-- sadValue doesn’t exist in the Example table
Basically, when we try to get the “happyValue” index (which is in our table), it will print it out right? But, when we try to get the “sadValue” index (which is NOT in our table), it’ll be nil
and fire the __index
metamethod.
You might see that in the code snippet, I’m setting __index
to a function with two parameters: t
and k
(which is just “table” and “key”).
The t
is the table that is trying to get that nil
index; in this case it’s the “Example” table. And k
is the value that doesn’t exist in our table (in our case it’s “sadValue”).
You see t[k] = k
? Well, basically I’m just setting k
in “Example” to k
. This is so that in the future if I’m tryna reference it, it won’t be nil
and the __index
metamethod won’t have to fire again:
-- t [k] = k
Example["sadValue"] = "sadValue"
--[[
["sadValue"] = "sadValue"
]]--
And the final thing you should see is this line: return k
.
Now what does this line mean? Basically, I’m returning k
which means when it prints Example[“sadTable”]
it’ll print k
or “sadTable”
__index
with two extra steps:
local Ex = {}
local T = setmetatable({}, Ex)
T.__index = Ex
Now what is this doing you might be asking. Well, remember how i said the __index
function fires when a child table or metatable attempts to get a nil index? Well, this fires whenever the table youre setting the __index
metamethod from (in this case T
).
Basically, if I did this:
print(T["ThisIndexDontExist"]) -> nil
-> __index is fired
The __index
metamethod would look thru the Ex
table to see if ThisIndexDontExist
(the key that we’re trying to get) is in there. If it can’t find ThisIndexDontExist
in the Ex
table, then it will return nil
(which means it would print nil
).
Most people will just do this though (you’ll see this in a bunch of ModuleScripts), it’s also the way I prefer:
local T = setmetatable({}, {})
T.__index = T
This code snippet is essentially the same as the code snippet below “__index
with two extra steps” but I don’t define an “Ex” table but I instead just add a table directly into the setmetatable
function.
There’s a couple other useful metamethods I won’t get into but here’s some other useful ones:
__newindex
__mode
-
__call
(not used as much anymore)
__concat
Before I finish writing this lengthy post, I should tell you about getmetatable
:
Basically, remember how I was talking about a “child table” or the table
parameter in the setmetatable
function? Well, getmetatable
has ONE parameter and it’s a table.
If the table you provide as the parameter has a metatable attached to it, it will return the metatable!
local Methods = {}
local Metatable = setmetatable(Methods, {})
Metatable.__index = Metatable
local Metatable2 = getmetatable(Methods)
-> Metatable2 is now equivalent to "Metatable"
-> Metatable2 is a clone of "Metatable"
There’s a really good post that @NotRapidV linked that has personally helped me understand metatables and metamethods better.
Yes I had to stretch out the explanation so I could explain it as throughly as I could.
Anyways, thanks for reading! I hope I explained it pretty well but in a bit simplistic way.
This is my longest reply yet here on the DevForum
and yes this was written on mobile
^
This no longer has the title, well for posts and reply wise (not just reply wise).
(had to fix the quotation marks obv in my 1/24/24 revision)