Questions for metatables

In all my years of scripting, metatables are probably the harder concepts for me to grasp. Some people say that its because I don’t know any OOP, but I disagree because I know a lot of OOP in Java and C++. I have heard the phrase “metatables just describe tables” being thrown around and I am not sure if I heard that correctly. Is this essentially correct?

local table1 = {"john", "bob"}
local metatable = {__index = table1}
setmetatable(table1, metatable)

Does the above code create the following:

table1 = {"john", "bob", {__index = table1}}

Second question: Say I have a module script that creates a gun class.

Gun = {} -- is this the base class?
Gun.__index = Gun

function Gun.new(damage, ammo, reloadTime)
    local newGun = {} 
    setmetatable(newGun, Gun)
    newGun.damage = damage
    newGun.ammo = ammo
    newGun.reloadTime = reloadTime
    return newGun
end

function Gun:Fire()
    while wait() do
        self.Position.X += 1
        --do other stuff, not going to list here
    end
end

In another script, if I call these two methods, will the following happen?

local Gun = require(ModuleScript.^^^^) -- this is the same as "import" from java?

local new_Gun = Gun.new(14, 23, 53) -- i assume this is instantiating the class?
new_Gun:Fire()
--------------------------------------------------------------------------------------------------------------
--Also is the bottom table correct?
Gun = {__index = Gun, {14, 23, 53}}

I would appreciate if someone cleared metatables up a little bit for me.

1 Like
  1. No, metatables do not alter the table content. Think of setting metatables as defining operator overloading functions for classes in C++.
  2. Gun.new (constructor) will return a table, containing the properties and namecalling that table (: operator) will access the member functions in the module with “self” being the dereferenced *this pointer (from C++).
    require() is kind of like importing a struct (maybe a namespace?) which has properties and functions inside of them.
    Objects created won’t be stored in the struct itself so you will only see its __index.

So in terms of java, metatables are kinda like interfaces? and they do not alter the table’s contents. Does that mean the metatable is outside of the table, but somehow describes the table?

I find it helps to think about metatables as a list of events a table can have. Want to know when a new value is added? Use the __newindex metamethod. What about when you try to multiply the table with something else? That’s __mul. There are a lot of these “events” lua-users wiki: Metatable Events.

These functions are not held inside your original table, they are held in the metatable and attached onto it modifying how it behaves. Metatables/metamethods are essentially lua’s implementation of operator overloads.

In your examples local newGun = {} creates an empty table and then the metatable setmetatable(newGun, Gun) is applied, notably with the __index metamethod. What that does is redirect any keys which have a nil value to the Gun table, which conveniently contains all the Gun methods. This way we don’t have to duplicate the functions into every table. Values in the metatable (Gun) can be thought of as static class values here.

Hope that helps.

1 Like

By events, do you mean convetionally connecting them like so?

table.__newindex:Connect(function()
--blahblah
end)

Or is it a special way to bind the event?

Yes, that’s why setmetatable() exists.

Metatables are literally tables used to redefine attributes of a table. You can use setmetatable(targetTable, metatable) to set the targetTable’s metatable. It also returns the targetTable.

So, you’d have a key __index and have a function associated with that key. The __index attribute is whenever the table is indexed and a nil value is found. You get the table it was from, plus the key used as arguments.

Further reading

lua-users wiki: Metamethods Tutorial


An example of using metatables: printing out a table cleanly. __tostring is used whenever the table is converted into a string. So, we can use that attribute to modify what get’s printed out.

local normalTable = {1, 2, 3, 4, 5}
local metatable = {
    __tostring = function(tbl) -- We get the source table as arg.
        local str = ""
        for _, val in next, tbl do
            str = str..", "..val
        end
        return str:sub(2) -- Return the string made
    end
}

print(setmetatable(normalTable, metatable)) -- 1, 2, 3, 4, 5

You can get a table’s metatable by using getmetatable(targetTable), if the metatable is not locked, it’ll return the metatable.

Hopefully, that clarified what they are.

Edits

1: clarification
2: metatables aren’t events, they’re really attributes

No these are special keys which can be found via that link. You can use them like this.

local t = {}
local meta = {}

meta.__index = meta
meta.__newindex = (function(table, key, value)
    print(key, "=", value)
    rawset(table, key, value)
end)

setmetatable(t, meta)

-- output "9  =  5"
t[9] = 5

-- or more conveniently
function meta:__mul(a)
    self.value = self.value * a
end

t.value = 10 -- value  =  10
print(t.value) -- 10
_ = t * 10
print(t.value) -- 100

Note: Confused with metatables - #11 by Club_Moo

It should be noted that calling them events could be confusing, as Roblox Events are something
else entirely.

Just a super tiny thing that I wanted to point out! In the long run, thinking of them as events might lead into confusion. Thinking of them as behaviors (changes to a table’s set of behaviors) is better.

I usually come across a question that goes along the lines of “why doesn’t lua have an __indexchanged or something that fires when an index is changed”. This is not what metamethods are designed for! Metamethods are to change (override would be a better word) an already existing behavior, and not use them to detect/listen to certain things. They’re not literally events, and the idea of a some sort of __indexchanged is probably inspired from the .Changed event.

In fact, one of the above posts might be an example of such confusion, or maybe just not knowing the grammar metatables follow, this one

__newindex if you want to know whenever a table field has changed. It works both ways, but I think it makes more sense to think of them as events.

I don’t think you got the point. Thinking of them as events leads into the idea that anything you do on a table should have its own metamethods. No.

The __indexchanged example is a perfect one, and I can think of more. Metamethods are overriding default behaviors, not listening for stuff you do with that table.

If you have some python knowledge, dunder methods, which are pretty much lua’s metamethods, are way more complicated than what you’d call an event.

Also, it doesn’t always work both ways, something like __mode for example isn’t an event.

1 Like

I don’t know much of python yet to learn abound dunder methods (never heard of them until now). But, I see the comparison after a quick read.

Also, I didn’t think of __mode, which doesn’t make sense as an event since it’s a string. So, it does make more sense to think of them as attributes then. I’ll append edit my post about metamethods then about this. Thanks for making it clear.

1 Like