Metatables: Explain like I'm a toddler

One thing I have trouble grasping on to the optional concept is metatables. I do understand it’s something they applied to by setmetatable(). I have read Lua documentation and researched through the internet for an easier concept (even through Devforum searching), but I still do not have the background knowledge on how it works for me to remember it and properly comprehend what are my next steps when utilizing metatables.

I really want to understand in order to conveniently somehow set a set of functions within a table that can be created. To be specific, I need help on the parameters of setmetatable(table, metatable) and why is it used in certain parts of the function where it’s appropriate in order to successfully create it and not cause an error. __index is also my enemy. I still do not get how it works either like what are its properties and purpose?

(Pspsps, I had trouble with remembering how to do for loop until I learned the purpose of i is to be the iteration index and v to be the value of the key used in table. If you guys can help me that way, thanks. I appreciate it if example is provided.)

1 Like

This is going to be very surface level on what you can do with metatables and whatnot, but hopefully this betters your understanding.

When I was learning metatables, I kind of thought of metatables and metamethods as connections to event objects in Roblox (if that makes sense, hear me out). Here’s what I mean:

An event you are probably aware of is RemoteEvent.OnServerEvent. You then use :Connect to connect a function to it.

With metatables, “metamethods” are kind of like these events, and the function you give the metamethod is the function you connect it with.

For instance, __index, according to the documentation is

Fires when table[index] is indexed, if table[index] is nil. Can also be set to a table, in which case that table will be indexed.

Think of __index as like an event for when the table is indexed. You can have a function for when the __index metamethod is fired.

local Table = {}
setmetatable(Table, {
   __index = function(table, index)
      print("This table has been indexed!")
   end
})

This prints when the table has been indexed and the index does not exist.

For more information (as I just scratched the surface of what you can actually do with metatables), visit these:

3 Likes

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)

6 Likes

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