Self returning nil

Hello!

I’m creating a OOP script, and for some reason self keeps returning nil. I’m using part of code samples provided by Roblox in CollectionService documentation. I have no idea of what’s wrong, every other topic I found didn’t solve the issue or was a different problem.

This is what is being outputted:

nil
nil
nil
...: Attempt to index nil with 'GetDescendants' [...]

This is some part of the code, I’m not providing the entire code.

local CollectionService = game:GetService('CollectionService')
local TweenService = game:GetService('TweenService')

local Door = {}
Door.__index = Door
Door.TAG_NAME = "Door"
Door.OPEN_TIME = 5

function Door.new(door)
	local self = {}

	setmetatable(self, Door)

	self.door = door
	self.debounce = false
	self.open = false
	self.locked = false
	self.clearance = door.Configuration.Clearance.Value
	self.combativeAllowed = door.Configuration.CombativeAllowed.Value
	self.openPos = {CFrame = door.Union.CFrame * CFrame.new(0,2.5,0)}
	self.closePos = {CFrame = door.Union.CFrame}

	self.keycard1Connection = door.Reader1.Attachment.ProximityPrompt.Triggered:Connect(function (...)
		Door:OnInteract(...)
	end)
	
	self.keycard2Connection = door.Reader2.Attachment.ProximityPrompt.Triggered:Connect(function (...)
		Door:OnInteract(...)
	end)

	return self
end

function Door:OnInteract(player)
	print(self.debounce) -- nil
	print(self.open) -- nil
	print(self.clearance) -- nil
	for index, inst in pairs (self.door:GetDescendants()) do -- Error! Attempt to index nil with 'GetDescendants'
		if inst:IsA('ProximityPrompt') then
			inst.Enabled = false
		end
	end
end
2 Likes

I don’t think you should name your variable self as self is a global variable…

1 Like

In that case I’m overriding the self global variable. But that’s not the issue though. I changed the variable name to test, and I still with the same issue.

How are you calling the function?

My code is being executed in a ServerScript, not in a ModuleScript. I tested with the code sample provided by Roblox on the CollectionService wiki page, and it works. I really don’t know what is wrong, I just added more properties to the Door class and that just started happening.

-- Code that I already shown is above this part, this is at the same script

local doors = {}

local doorAddedSignal = CollectionService:GetInstanceAddedSignal(Door.TAG_NAME)
local doorRemovedSignal = CollectionService:GetInstanceRemovedSignal(Door.TAG_NAME)

local function onDoorAdded(door)
	if door:IsA("Model") then
		doors[door] = Door.new(door)
	end
end

local function onDoorRemoved(door)
	if doors[door] then
		doors[door]:Cleanup() -- Don't worry, cleanup is defined
		doors[door] = nil
	end
end

for _,inst in pairs(CollectionService:GetTagged(Door.TAG_NAME)) do
	onDoorAdded(inst)
end
doorAddedSignal:Connect(onDoorAdded)
doorRemovedSignal:Connect(onDoorRemoved)

This part of the code has been entirely taken from Roblox’s code samples, but I changed from “BasePart” to “Model” in onDoorAdded.

self is not a global variable, it’s only a common variable name for a reference to a method’s own table.

1 Like

I started re-writing the code entirely, and this managed to fix the error I mentioned in that function. I don’t really know what the issue was. I’m still using a good part of the code that was returning errors, but now the same error that I first mentioned jumped into another function.

The function:

function Door:SetOpen(isLocked)
	print(self.open) -- nil
	if not isLocked then
		if self.open then
			local tween = TweenService:Create(self.door.Union, tweenInfo, self.closePos) -- Error! attempt to index nil with 'Union'
			tween:Play()
			tween.Completed:Wait()
			self.open = false
			wait(0.25)
			self.debounce = false
			for index, inst in pairs (self.door:GetDescendants()) do
				if inst:IsA('ProximityPrompt') then
					inst.Enabled = true
				end
			end
		else
			local tween = TweenService:Create(self.door.Union, tweenInfo, self.openPos) -- Error! attempt to index nil with 'Union'
			tween:Play()
			tween.Completed:Wait()
			self.open = true
			wait(0.25)
			self.debounce = false
			for index, inst in pairs (self.door:GetDescendants()) do
				if inst:IsA('ProximityPrompt') then
					inst.Enabled = true
				end
			end
		end
	end
end

This function is being called from inside of the OnInteract function as Door:SetOpen(false).

I think I found the original problem.
All instances of Door:OnInteract(...) need to be object:OnInteract(...) or self:OnInteract(...) depending on where they are located of course and what you are doing.

Particularly here, they need to read self:OnInteract(...)

3 Likes

Did you call the method with : not .?

If you call with . it wont pass the table itself as self. You should also call it on the object itself, not the module.

Its also considered good practise to have the constructor disconnected from the functions, to prevent it leaking into the public interface, better encapsulating the object.

local Cat = {}
Cat.__index = Cat

function Cat:Meow()
  print("Meow")
end

return function()
  local o = setmetatable({}, Cat)
  --this is the constructor
  return o
end
2 Likes

Did you read my post? self doesn’t define itself automatically, neither is it a global variable. You need to define self in the parameters, change the method declaration character from : to . and call the method using :.

The method has to be defined using . for self to be passed.

I recommend you read the Lua docs again because this is not true

local t1 = {a = 5}

function t1.foo()
  print(self.a) --> ERROR: attempt to index nil
end

function t1:bar()
  print(self.a) --> 5
end

table:method() is syntax sugar for table.method(self), calling a table with : automatically passes self into the interface, where self is the table itself.

There’s an exception to this with Roblox Instances as they have a __namecall metamethod, but that’s not relevant here.

3 Likes

No one has addressed the original problem so I will explain it, and no, it is not about declaring self as a local variable, which is totally fine.

The Door.new function successfully changes the fields’ values of the local self table, but it also sets the metatable Door to it, which has an __index metamethod, meaning that trying to index it will redirect the index to another value (in this case, the metatable Door itself). Since there is no __newindex metamethod, setting new indexes will not be redirected, and will be set for the table itself (self).

So basically, when you are trying to get the field values, you are getting redirected (by __index) to the Door table, when the values are actually in the self table itself.

There is a straightforward fix for this: Avoid using Door as a metatable and do not use a metatable at all. I simply do not see why a metatable is useful in this case. At least start using it once you are completely sure of how metatables work, then there should be no confusion in case there are issues like this.

I believe the OP is basing this off the example provided in Lua’s docs, which doesn’t create a very good constructor setup, the way Roblox does it is much better.

Roblox have it setup so that the functions for each method is only ever declared once, aswell as an __index metamethod pointing to itself.

Code samples extracted from ChatChannel, one of Roblox’s core chat scripts

local methods = {}
methods.__index = methods

function methods:SendSystemMessage(message, extraData)
	local messageObj = self:InternalCreateMessageObject(message, nil, true, extraData)

	local success, err = pcall(function() self.eMessagePosted:Fire(messageObj) end)
	if not success and err then
		print("Error posting message: " ..err)
	end

	self:InternalAddMessageToHistoryLog(messageObj)

	for i, speaker in pairs(self.Speakers) do
		speaker:InternalSendSystemMessage(messageObj, self.Name)
	end

	return messageObj
end

function module.new(vChatService, name, welcomeMessage, channelNameColor)
	local obj = setmetatable({}, methods)

	obj.ChatService = vChatService
        return obj
end

The file has been heavily truncated and likely wont work on it’s own.

This way of doing it is better for two reasons:

  1. You’re not leaking the constructor into the object itself, for better encapsulation (i mentioned this in a previous post)
  2. The functions are only ever declared once, and then each table references the same memory pointer.

The issue with the OP was that they were calling the Door table instead of the object itself, as again, metioned above,

1 Like

There is only one good reason to use metatables at all: Exposing it as public API, making it easier for users. I do not think that is the case for the OP.

1 Like

Change Door:OnInteract to self:OnInteract and put it inside the Door.new function just before the return self line.

That’s because the object you’re creating and returning with Door.new constructor is not a Door object but a new object assigned to variable self in your constructor function. In connection with that, self in Door:OnInteract function will point to Door, not the new object you created.

1 Like

Issue resolved by @JarodOfOrbiter and @Kacper, I needed to call the method as self:OnInteract and self:SetOpen.

1 Like