OOP - 'self' is not acting as intended

Hello devs,
I was writing a module, but encountered this bug which I already had by the past. Basically, ‘self’ keyword acts strangely outside .new() function.

local Module = {}
Module.__index = Module

function Module.new()
    local self = {}
    self.a = 1
    self.b = 15
    return setmetatable(self, Module)
end 
 
function Module:AddAToB()
    print(self)
    return print(self.a + self.b) -- errors
end
function Module:DoSomthing()
end
return Module

Expected Output:

>> {["function"] = DoSomthn, ["function"] = new, ["function"] = AddAToB a = 1, b = 15},
>> 16

Current Output:

>> {["function"] = DoSomthn, ["function"] = new, ["function"] = AddAToB}, 
>> attempt to perform add (+) on nil

Thanks!

again. to get self switch . to :

You mean using self:a instead of self.a ?

no

You see that dot in Module.new? make it Module:new()

Try this instead:

function Module.new()
    local self = {}
    setmetatable(self, Module)
    self.a = 1
    self.b = 15
    return self
end 
1 Like

I’ll try that, but how does it differs from .new() ?

with : it makes self exist, thats a rule for OOP.

Actually see if this works:

function Module.new()
    local newModule = {}
    setmetatable(newModule, Module)
    newModule.a = 1
    newModule.b = 15
    return newModule
end 

I though that since .new() is a constructor function it was no need to get :new() instead

let someone else explain it. im no expert at OOP

Most modules have a .new function or something similar that creates a new meta table which gives it access to all the :functions the . In his .new function is fine.

@XRHTech would be correct

How are you calling the function :AddAtoB()?

I think you are doing this:

local Module = require(theModule)
Module:AddAToB()

You should be doing:

local Module = require(theModule)
local new = Module.new()
new:AddAToB()
1 Like

No no I am not, i do .new() and create new instance of module as you written

Got it working by replacing . with : and removing the self = {}

1 Like

I just tested your module in a blank place. Everything seems to be working fine.

local module = require(game.ReplicatedStorage.ModuleScript)

local new = module.new()
new:AddAToB()

image

In OOP, its not necessary call a constructor with a :, like Module:new().
When using the : indexer, it calls the function while passing the table it exists on (in a metatable, it passes the main table instead).

local Table = {}

Table.a = 1

function Table:GetA()
	return self.a
end

In this code, I made a simple table with a value and a function where the function takes self.

local a1 = Table:GetA()

This code will return the value of index a properly, no errors.

local a2 = Table.GetA() -- errors

But calling it like this makes an error: attempt to index nil with 'a' or in lua demo attempt to index a nil value (local 'self'). What happened? Why is it nil? Well, since I called the function with a ., it did not pass a self table. This actually means that you can call it with a . and pass a different self table.

local differentTable = {}
local a3 = Table.GetA(differentTable)

Although this won’t error, the code tries to index a blank table with a, which is a nil value.

Going back to the constructor thingy, calling it with a : returns Module. Here is the problem with your solution: doing it like this, actually makes a new index (or overwrite) an index in the class itself instead of the object.

local Module = {}
Module.__index = Module

function Module:new()
	self.a = 1
	self.b = 15
	return setmetatable(self, Module)
end 

This will create an index a and b in the module, not the object. Thus, printing the table will result in:

{
	["new"] = "function",
	["AddAToB"] = "function",
	["DoSomthing"] = "function",
	["a"] = 1,
	["b"] = 15
}

There are two values in the Module table which should not exist.

If you followed the lua guide, for some reason, the guide uses a : indexer to call the constructor class which is weird, but it creates a new object o, unlike just using the self table.

You said you did it like this:

local Module = require(theModule)
local new = Module.new()
new:AddAToB()

Which I don’t think you did correctly.

Since you said @commitblue’s solution worked (which is not the right solution, see my previous post), you probably did it like this

local Module = require(theModule)
local new = Module.new()
Module:AddAToB()

What is different? Well, you called the function through the Module table, not the new object, thus you have the error "attempt to perform arithmetic (add) on number and nil. But when you followed his solution, it worked.
So what is wrong with @commitblue’s solution? Well, if you read my previous post, I said that it creates a new value in the Module table, then since you called the function through the Module table, it successfully took the value from the wrong table. This means that creating a new object overwrites the current value.

You are actually right in the .new part, except you should call the function like new:AddAToB() not Module:AddAToB() so revert any changes and call the function through the new object not the Module table.

I could also be wrong, but from your output, thats what I understand.

This is off-topic now, but its better to just copy the output or screenshot it instead of copying it by typing. What do I mean?

That made me think you copied it by typing, the actual output should be:

{["DoSomthing"] = "function", ["new"] = "function", ["AddAToB"] = "function"}
1 Like

: passes self automatically, and will be the first parameter.

function Module.new(Your_Self, ...) end

. does not pass self, you need to pass that yourself.

function Module:New(...) end

The colon is for implementing methods that pass self as the first parameter.
So Module:new(1, 2, 3) should be the same as Module.new(x, 1, 2, 3)

as i said, Im no OOP expert or even beginner. I only know the bare bare basics.