In Roblox Studio, .self is often used in the context of Object-Oriented Programming (OOP). It’s a way to reference the current object or table you’re working with.
Source: Conversation with Copilot, 6/22/2024 16:01
self is typically used in Object-Oriented Programming and OOP-related programming to refer to the current object you’re working with. It’s not just specific to Luau, it’s widely recognised in this context even for languages that don’t promote such a name (e.g. Python, there is no keyword for self). I like to think of it as a standard.
I like to follow this rule when programming;
your code should be well enough commented and readable enough so that if another programmer read it they would know what the code is doing.
self adds to this as it tells someone you are working with a class or object.
TLDR: self helps to differentiate objects of classes from other variables or constants.
To answer your question super simply, yes, it is syntactic sugar. However, you should use it both in declaring and consuming member functions on classes in Lua / Luau.
Let’s explore this a little in a small class example in Luau. Because Part is actually a Roblox Instance name / primitive type, it’s not a valid example class, so let’s name our class MyPart.
ModuleScript MyPart
--!strict
export type MyPartType = {
__index: MyPartType,
new: (name: string) -> MyPartType,
PrintName: (self: MyPartType) -> (),
Name: string,
}
local MyPart: MyPartType = {} :: MyPart
MyPart.__index = MyPart
function MyPart.new(name: string): MyPartType
local self: MyPartType = {} :: MyPartType
self.Name = name
return setmetatable(self, MyPart) :: any
end
-- function MyPart.PrintName(self: MyPartType) would also work.
-- Generally avoid using . instead of :
function MyPart:PrintName()
print(self.Name)
end
return MyPart
Script consuming MyPart. Put this under the same parent.
--!strict
local MyPart = require(script.Parent.MyPart)
local myPart = MyPart.new("test")
myPart:PrintName() -- Just like in declaration.
myPart.PrintName(myPart) -- this also works
-- again, generally avoid . vs : for member functions
Note that if you use strict in the consuming code, it enables checks like the following in Studio:
Another Studio feature - automatic completion on : methods:
self does represent the Object, however in Object oriented programming, self represents the current object, where as in your case Part is the module.
When firing self:PrintName() you pass on the entire object.
When firing Part:PrintName() you only pass on self vars/properties defined in the context/environment. Meaning self properties declared in other functions will not be passed onto the function it’s firing to.
Think of it as Part is the Object, By default it includes the functions, __index, and if you have one ClassName/SuperClass, no variables are directly on Part, Part represents the entire module, so think of it as Global.
self is the current Entry under the object, it’s the __index, hence Index representing the index of an array/table. self inherits from the Global Object Part, but it has it’s own properties.
So part looks like:
local Part = Class:Extend("Part")
--- Here's what it looks like
{
__index = ...,---infinitely many (known as Cyclic)
ClassName = "Part",
SuperClass = ...,-- some people have one others don't
...all other Functions, like New/OnNew
}
self looks different, It’s apart of that infinite cycles under __index, self only inherits the Functions. Self is basically like an entry under Part.
self -- Defined when inherited using :
function Part:OnNew()
self.Base = self.Base -- Something passed through, ex: require(...):New{Base = Part}
self.BaseName = "Part"
end
--- Now if you were to print self inside OnNew, you'd get
{
["OnNew"] = function: 0xf3fc2041e3be0bf83z, -- Function id, representing OnNew.
["Base"] = Part, -- "BasePart"
["BaseName"] = "Part"
}