Setting __index in different ways

I see that in OOP tutorials people use this:

module.__index = module
someTable = setmetatable({}, module)

But is it any different from using this?:

someTable = setmetatable({}, {__index = module})

If not, I would appreciate if someone would explain this in simple terms.

1 Like

Since index is a standalone metamethod, both will work fine. The only reason I believe it is attached to the table is for ease of access. As long as index points to a table or function they are both valid uses. The second metamethod can be used to hide the method from intellisense, though it can get less optimized if it isn’t a constant. Here, the metamethod would be created every time an object is created.

2 Likes

in this case you would be able to recursively access __index value of the module for example

module.__index = module
someTable = setmetatable({}, module)

print(someTable.__index.__index.__index.__index) -- you could do this forever

in this case you are not able to access __index value at all (unless you will use getmetatable function of course)

someTable = setmetatable({}, {__index = module})

print(someTable.__index) -- nil

both are valid it is just that you might not want that recursive access to __index value or even having __index in the hint list while coding just like @2jammers have said

4 Likes

I am relatively new to scipting so I have a few questions.

As I have found with a brief Google search, IntelliSense is like an autocomplete. But why would you want to hide it from it? Also, I am not sure what you mean by a “constant” and why and what exactly will get less optimized. Lastly, as I understand, in my second example, the “__index” metamethod is created every time, but does this have any effects compared with the first example where it is not created every time?

1 Like

I know of recursion, when a function calls itself repeatedly, but not sure what “recursive access to __index value” means. Also, what is a “hint list” and what are the advantages/disadvantages of not being able to access “__index” value?

1 Like

Since the value of __index is set to the table that it is being stored in every time you index __index the value of that entry would be the initial table

{
    __index = reference to itself
}

i think nested array would be a better name for this
As it is the same as below

{
    __index = {
        __index = {
            __index = and so on forever 
        }
    }
}

roblox does something when you type something like this to prevent you from running out of RAM although im not sure what exactly and where you could look that up

2 Likes

hint list is a list that appears while you are coding to show what is accessable, what functions, variables are here by autocompleting your text

there are no advantages or disadvantages in having __index in the hint list it is just your preference whether you want it to display here or not
for example you are making some kind of module script

local HelperModule = {}

HelperModule.func1 = some function
HelperModule.func2 = some function 

HelperModule.__index = HelperModule

local MainModule = {}

function MainModule.new(arg1, arg2)
    return setmetatable({arg1, arg2}, HelperModule)
end

return MainModule

after requiring the module from another script

local Module = require(ModulePath)
local Object = Module.new(something, something2)

Object.

at this point a hint list so called intellisense should appear showing possible auto completions e.g properties, methods of that object

in this list you are going to see __index property if you are using the first method which you might not like as it doesnt add any functionality to the module and just hanging in there without a purpose

you might want to use the second method of setting metatables just to make your module look more clean in the hint list

2 Likes

So, the only difference in the two uses of __index is basically whether it will appear in the hint list or not?

i personally use the second method as the first one is here only because roblox made it possible this way otherwise you would get stack overflow

but yeah

1 Like

Alright, I’ll give you the solution unless someone else has to say anything.

Found a post that talks about how to solve the problem with unnecessary things popping up in the hint list. Just for anyone interested in this.

The way you choose to set __index should depend on the nature of the value to which it is to be assigned.

In this way, the main issue is that you are going to create and assign a new metatable {__index = module} each time but all of these metatables will have the same content. Why create and assign 100 metatables with the same content when you can assign a unique metatable for all of them ?

For the sake of efficiency, we prefer to use the following approach :

However, there are situations where it will be necessary to create a new metatable for each call. So, as I said at the beginning, the way you choose to set __index should depend on the nature of the value to which it is to be assigned.

1 Like

By unique you mean one metatable for all of them? Also, in what way is it more efficient? Does it run faster, is it less laggy or something else? And if its not hard for you, could you provide an example where you would need to crete a new metatable for each call and an example where you would need to have only one metatable for all other tables, and why exactly you would use one method over another?

Yes.

It’s mainly the memory that will be most affected, creating so many identical metatables instead of using the same metatable will just overload memory.

Having to create a metatable for each table is often needed when using private properties (properties that cannot be accessible through the table).

Here’s a ModuleScript containing code to create a table with a method movePart() which will be associated with a Part (the Part is the private property, it cannot be accessed via the table). Each time movePart() will be called, a Tween will be created and played to move the associated Part to its new position.

local TweenService = game:GetService("TweenService")
local Block = {}

function Block.new(part)
	local methods = {}
	
	function methods:movePart(target)
		local tween = TweenService:Create(part, TweenInfo.new(.2), {Position = target})
		tween:Play()
	end
	
	return setmetatable({}, {__index = methods})
end

return Block

As you can see, I create a metatable for each call because each metatable need it’s own methods since each one will have it’s own Part to move and it cannot be accessed through the table (it’s a private property).

Here’s an example of code using this ModuleScript.

local Block = require(script.Block)
local block = Block.new(workspace.Part)

while true do
	block:movePart(Vector3.new(4*math.random(1, 4), 2, 4*math.random(1, 4)))
	task.wait(.5)
end

Actually, using one metatable for all other tables is more a way to avoid overloading memory than a need.

Let’s take the same ModuleScript that was used previously, except that this time the Part is no longer be a private property (it will be accessible through the part field in the table).

local TweenService = game:GetService("TweenService")
local Block = {}

function Block.new(part)
	local self = {}
	self.part = part
	
	local methods = {}
	
	function methods:movePart(target)
		local tween = TweenService:Create(self.part, TweenInfo.new(.2), {Position = target})
		tween:Play()
	end
	
	return setmetatable(self, {__index = methods})
end

return Block

Here’s what happens.

local Block = require(script.Block)

local blockA = Block.new(workspace.PartA)
local blockB = Block.new(workspace.PartB)

print(getmetatable(blockA) == getmetatable(blockB))
-- false because each block have it's own metatable

Each table still has its own metatable, and that’s a problem, because each call will add a new metatable to memory, and it’s possible to avoid this now that there’s no longer any private property. Here’s what the ModuleScript should look like instead.

local TweenService = game:GetService("TweenService")
local Block = {}
Block.__index = Block

function Block.new(part)
	local self = {}
	self.part = part
	
	return setmetatable(self, Block)
end

function Block:movePart(target)
	local tween = TweenService:Create(self.part, TweenInfo.new(.2), {Position = target})
	tween:Play()
end

return Block

As you can see from the examples, the method I choose will therefore generally depend on whether or not there are private properties.

1 Like

yeah why to create a new table for each setmetatable call while all you need is to predefine that table and reference it in later uses
for example

local Metatable = {__index = module}

local Metatables = {}
for i = 0, 10 do
    Metatables[i] = setmetatable({}, Metatable)
end

which would be exactly the same as the first method as you dont create one more table {__index = module} each time

is one method more efficient over another? no they are the same

2 Likes

I am a bit confused. Is @Ledlouis_10 right when he says that one method is more efficient over another, or @V_ladzec is right when he says that they are equally efficient?

I think @V_ladzec was talking about these 2 methods when he said they were the same and not about the one that creates a new metatable with each call :

Here’s a comparison of memory consumption for each of the 3 methods.


We can see that method 1 and method 3 are the same, as @V_ladzec said, while method 2 consumes almost twice as much memory (because a new metatable is created for each call).

Method 1 :

local module = {}
module.__index = module

local someTables = {}

while true do
	local someTable = setmetatable({}, module)
	someTable.value = math.huge
	table.insert(someTables, someTable)
	task.wait()
end

Method 2 :

local module = {}


local someTables = {}

while true do
	local someTable = setmetatable({}, {__index = module})
	someTable.value = math.huge
	table.insert(someTables, someTable)
	task.wait()
end

Method 3 :

local module = {}
local mt = {__index = module}

local someTables = {}

while true do
	local someTable = setmetatable({}, mt)
	someTable.value = math.huge
	table.insert(someTables, someTable)
	task.wait()
end
2 Likes

I understand it now. Thank you.

1 Like

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