How to Get `Self` from metatable

I’ve searched through multiple Dev Forum and YouTube posts about metatables and how they work. However, I haven’t seen much specifically talking about how self is passed.

I know that when you do this, self is automatically passed.

function mt:MyMethod()
    -- ...
end

Specifically, my issue is the ItemAdded not being able to pass down the self value to the Connect function.

local superTable = setmetatable({
	["_itemAddedFunctions"] = {}, 
	["_itemRemovedFunctions"] = {}
}, {
	__index = {
	
		AddItem = function(self, item)
			table.insert(self, item)
			for _, f in ipairs(self._itemAddedFunctions) do
				task.spawn(f)
			end
		end,
	
	ItemAdded = {
		Connect = function(self, f)
			table.insert(self._itemAddedFunctions, f)
		end
	}
}})

local function test()
	print("item added!")
end

superTable.ItemAdded:Connect(test)
superTable:AddItem("my item")

Any help would be appreciated. Thank you!

1 Like

And the error message:

invalid argument #1 to 'insert' (table expected, got nil)

Edit: It’s probably talking about self._itemAddedFunctions == nil since self wasn’t defined and carried down in the first place.

1 Like

If you call a function of an object with a colon instead of a period, the object is passed implicitly as the first argument, self. Additionally, if you define the function of an object with a colon instead of a period, self will already be implicitly defined as the first argument.

For example, if we create an object as

local Object = {}
Object.__index = Object

function Object.new()
	return setmetatable({}, Object)
end

local obj = Object.new()

then both of these lines of code are functionally identical:

obj:DoSomething() -- colon operator implicitly passes obj as first argument

obj.DoSomething(obj) -- period does not pass obj as first argument, we must pass it ourselves

and for defining the function, both of these methods will also be functionally identical:

function Object:DoSomething()
	print(self)
end

function Object.DoSomething(self)
	print(self)
end
2 Likes
local mt = {}

local superTable = setmetatable({
	["_itemAddedFunctions"] = {}, 
	["_itemRemovedFunctions"] = {}
}, mt)

mt.__index = {

	AddItem = function(self, item)
		table.insert(self, item)
		for _, f in ipairs(self._itemAddedFunctions) do
			task.spawn(f)
		end
	end,

	ItemAdded = setmetatable({
		Connect = function(self, f)
			table.insert(self.super._itemAddedFunctions, f)
		end
	},
	{
		__index = {
			super = superTable
		}
	}
	)
}

local function test()
	print("item added!")
end

superTable:AddItem("my item")
superTable.ItemAdded:Connect(test)

self will always refer to the table that a function is directly called from when the semicolon is used. ItemAdded:Connect() is the same as ItemAdded.Connect(ItemAdded). This is how it is intended to function - self is used to refer to the direct table a method was called on. However, to get the value of the table the table is nested within, we can split the setting into multiple lines so that we can refer to the superTable variable within the __index of the metatable. Then we can use another nested metatable to pass a reference value of the superTable into the ItemAdded table.

This is clearly a roundabout method but it gets the job done.

EDIT: Technically you don’t need to use a nested metastable if you are fine with just directly doing superTable instead of self.super in the Connect function.

2 Likes

Thank you so much for the clear explanations on how to get the self value.

I wanted to mark both of your solutions as solution but looks like Roblox can’t make that happen.

I will mark the last post as solution, since I was able to both understand what was going wrong and learn about self at the end of reading all of you all’s responses! :slight_smile:

1 Like

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