What can metatables be used for?

I often see that they are used, but I do not understand how to use them myself, what exactly it can be useful for, please give an example of how metatables can be used.

4 Likes

So from what I understand with metatables is (might be inaccurate):

A metatable is a child to another table basically (so the other table is the parent). And basically when something weird happens the metatable consults the Parent and the Parent decides on what is going to happen.

More to learn here:

3 Likes

Yes, I’ve read this topic, but unfortunately I still haven’t found a case to use metatables, everything that is offered there is somehow easier for me to do without using them.

3 Likes

__index metamethod example

local normalTable = {mike = "manhattan", george = "france"}
local metatable = {
    __index = function(self,index)
          print(index) --hello
          print(self==normalTable)--true
          return "not found"
    end
} 
setmetatable(normalTable,metatable)
print(normalTable.hello) --not found
print(normalTable.mike) --manhattan

__call metamethod example

local normalTable = {}
local metatable = {
    __call = function(self,index)
          print(index) --hello
          print(self==normalTable)--true
          return "not found"
    end
} 
setmetatable(normalTable,metatable)
print(normalTable("hello"))--not found
3 Likes

In simple words, you can create your own classes with metatables. For example, you can create a “Gun” class, then use it to easily make guns for your game by just changing properties of the class. If you want to change something for all guns, all you need to do is change the code of “Gun” class.
Metatables are very handy if you want things to be organized, optimized and easy to use.

3 Likes

They are most commonly used for Object Oriented Programming(OOP) in Roblox

2 Likes

If metatables aren’t the thing for you, that is completely fine. DOP and OOP both have their pros and cons.

2 Likes

To answer your question, I’d say that using metatables makes it easier and more convenient to improve code performance. Here’s why :

One the main benefits of using metatables is about Object-Oriented Programming. You may have seen that classes are often defined in this way in Luau/Lua :

local Number = {}
Number.__index = Number

function Number.new(value)
	local self = setmetatable({}, Number)
	self.value = value
	
	return Number
end

function Number:IncrementValue()
	self.value += 1
end

return Number

The reason why we should use metatables is because without using metatables, defining the same class would looks like this :

local Number = {}

function Number.new(value)
	local self = {}
	self.value = value
	
	function self:IncrementValue()
		self.value += 1
	end
	
	return self
end

return Number

In these 2 versions, the instances created will be used in the same way:

local number = Number.new(42)
number:IncrementValue()

The problem with the latest version is that each instance of the class will have its own method, which will be identical to those of the other instances of the same class.

This is a problem since the use of a metatable can avoid the need to create a copy of the method for each instance and will instead give a single method shared between all instances of the class thanks to the metamethod __index.

In this case, we could also say that we can solve this problem without using a metatable by defining the class as follows :

local Number = {}
local Methods = {}

function Number.new(value)
	local self = {}
	self.value = value
	self.Methods = Methods
	
	return self
end

function Methods:IncrementValue()
	self.value += 1
end

return Number

But now we have to use the class like this :

local number = Number.new(42)
number.Methods.IncrementValue(number)

So it starts to get complicated to solve the simple problem of duplicating a function unnecessarily. That’s why using a metatable is the perfect solution here.

3 Likes

Metatables are usually used in Object-Oriented Programming (OOP) which you can use to create instances in Lua. Actually, Roblox used this to create instances and datatypes such as Instance.new() and Vector3.new().

Other than that, you can use those to make your tables even more powerful and stronger! Metatables work with the use of metamethods! If you don’t know what metatmethods are, they’re like “events” of a table, such as the PlayerAdded event of Players and the Touched event of BasePart. If you want to know more about metamethods, check these

https://www.lua.org/manual/5.1/manual.html#2.8
Here are some of the commonly used metamethods:

  1. __index - This triggers if you’re trying to get something in a table but it’s not there! This can be a function that returns something if someone tries to get an unexisting object from the table OR another table that will be used to get the object if it is not in the table.
local myFruits = {
    Apple = "Apple",
    Banana = "Banana"
}

print(myFruits.Apple) -- prints "Apple" because it exists inside myFruits
print(myFruits.Banana) -- print "Banana" because it exists inside myFruits
print(myFruits.Orange) -- prints "nil" because it doesn’t exist inside myFruits

local extraFruits = {
    Orange = "Orange",
    Strawberry = "Strawberry",
    Lemon = "Lemon"
}

local metatable = {
    __index = extraFruits -- if a fruit is not in myFruits, we'll use extraFruits instead
}
setmetatable(myFruits, metatable) -- setting metatable as the metatable of myFruits

print(myFruits.Apple) --> Apple
print(myFruits.Banana) --> Banana
print(myFruits.Orange) -- now it prints "Orange" instead of "nil"
  1. __newindex - This triggers if you’re setting a new value in a table. In other words, if you have a table called “myFruits” and it only contains “Apple” and “Banana”, then you’re trying to add a new fruit called “Orange”, __newindex will run or trigger. This can only be set as a function with the parameters self, index, and value.
local myFruits = {
    Apple = "Apple",
    Banana = "Banana"
}

local thisIsATable = { -- you can also name your metatable other than "metatable"
    __newindex = function(self, index, value)
        print(self, index, value) -- this will print the table myFruits and the name of the fruit being added
        print("This is not my fruit!")
    end
}
setmetatable(myFruits, thisIsATable)

myFruits.Orange = "Orange"
-- doing this will trigger __newidex
-- printing "This is not my fruit!"
  1. __add – In my opinion, this is one of the coolest! Using this will make you do anything when you’re trying to add a table to a number! As the name suggests, this will trigger if you’re trying to add the table to a number or another table using + sign. This is a function with the parameters self (the table in which the metatable is set to) and value (the number or table you’re trying to add to the table) that returns the result after “adding”.
local myTable = {
    Foo = "Foo",
    Bar = "Bar"
}

local meta = {
    __add = function(self, value)
        return 5
    end
}
setmetatable(myTable, meta)

print(myTable + 5) -- prints "5" because it is the number returned by __add
print(myTable + 1) --> 5
print(myTable + 9999999999999999) --> 5
print(myTable + 0) --> 5
print(myTable + {}) --> 5

There are many more metamethods out there so feel free to check them out! If you have any questions, feel free to ask me!

2 Likes