What is up with the dots in classes?

So I was learning everything I could find about metatables and got at a moment stuck. I asked myself some questions.

For example let’s look at this code that declares a metatable, and with it, I initialize some functions.

local metatable = {}
metatable.__index = metatable

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

function metatable.printHello(self, string) -- As you can see I'm using only the dot -> .
	print(self, string)
end

local t = metatable.new()
t:printHello("Hello") -- prints "{} Hello"

The output is specified in the code ^

But what if at line 10 I put 2 dots instead of 1?

Result:

local metatable = {}
metatable.__index = metatable

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

function metatable:printHello(self, string) -- As you can see I'm using only the 2 dots -> .
	print(self, string)
end

local t = metatable.new()
t:printHello("Hello") -- prints "Hello nil"

And also for some reason it highlights me the self parameter at line 10 with orange line. Why?

Here are the combinations for line 10 and 14:

  1. dot and 2 dots → “{} Hello”
  2. dot and dot → “Hello nil”
  3. two dots and two dots → “Hello nil”
    4.two dots and dot → “nil nil”

I want to know what these “dot” specifiers mean in classes and what are the results. And also why for combination 3 and 4 it highlights with orange line the self parameter.

1 Like

There is an important difference versus using a . instead of a : and vice versa. It’s hard to explain without context, so let me give you some:

Take this example ModuleScript being used to define a custom class Foo. We’ll give it a method that adds a passed value to an internal class variable amount:

local Foo = {}
Foo.__index = Foo

function Foo.new()
  local newFoo = {}
  setmetatable(newFoo, Foo)
  newFoo.amount = 0
  return newFoo
end

function Foo:add(amnt)
  Foo.amount = Foo.amount + amnt
end

return Foo

Calling add(amnt) on an object of class Foo will, as expected, add amnt to the internal amount variable contained in the object.

local Foo = require(FooModule)

local Foo1 = Foo.new(10)
Foo1:add(10) -- amount in Foo1 is now 12

You are wondering why we used a : operator to call the add function. This is why:

Foo:add(var) is equivalent to Foo.add(self, var). In other words, the : operator creates a hidden self parameter that can be used to refer to the object the method is being used on.

This is why you’re getting such strange results when you mix and match the . and : operators. The new() function doesn’t need a : because there is no reason to pass an object of a class to a constructor function that is going to create that object anyways. When you execute methods on an object of a class, however, the : operator allows you to access that object.

The difference is that the : operator negates having to pass in the object you want to modify whenever you use a method of the class; in other words, it allows you to call methods on an object of a class directly.

-- No colon
-- An object of class Foo must be passed as the first parameter self
function Foo.add(self, var)
  self.amount = self.amount + var
end

-- With colon
-- This can only be called on objects of class Foo!
function Foo:add(var)
  self.amount = self.amount + var
end

Refer here for more information. The orange line in Studio is probably just a result of improperly using the : and . operators. Code I provided may be incorrect, but you get the idea.

5 Likes

More specifically, Table:Function(...) is syntax sugar for Table.Function(Table, ...). When it comes to classes in Luau, the instance of the class (object) is just a table of properties—it does not contain any methods. It’s the class that typically contains the methods. The object is linked to a metatable that defines behaviour for when values indexed in the object do not exist, and that behaviour is to search the class table for those values and return them if found. This is how objects can use their class’ methods without carrying a copy on themselves.

Altogether:

object:Method(...)

Translates to:

class.Method(object, ...)

And as @Negativize said, functions whose signatures are in the following format will automatically consume the first argument under the implicit parameter “self”:

function Table:Function(...)
1 Like

So like when I’m using the single-dot operator in class I’m using it just to initialize the metatable like setmetatable({}, myMetatable)?

And when using the two-dot operator I’m basically initializing functions to, for example calculate the numbers in the table, you get the point.

Correct me please if I’m wrong please

Sorry for late response…

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