Why is the .__index there?

Hii all! I am currently learning OOP and all this stuff. I saw this post: All about Object Oriented Programming and they have this code:

Car = {}
Car.__index = Car

function Car.new(position, driver, model)
    local newcar = {}
    setmetatable(newcar, Car)

    newcar.Position = position
    newcar.Driver = driver
    newcar.Model = model

    return newcar
end

function Car:Boost()
    self.Speed = self.Speed + 5
end

Could someone explain to me why they are doing: Car.__index = Car . What is it doing, what reason does it have and overall I am superrrrrr confused. :sob:

Both parameters of setmetatable are tables. A metatable has metamethods which are special keys that perform certain functionality for tables. __index is written there so that when setmetatable is called, it’ll pick up the “Car” table as the __index metamethod. As it points to a table, when the first table of setmetatable doesn’t have the index you want to access, it’ll look through the __index table.

It’s a shorthand for this:

local Car = {}

function Car.new()
    setmetatable({}, {__index = Car})
end
3 Likes

I’ve only used it in one way, but I can tell you it’s not really that necessary, but a quality-of-life thing to have just incase.

.__index can be used as a global variable for other modules, here’s an example;
Let’s say you have 2 modules & 1 script, the first module containing an index and the other using the first module’s index via setmetatable(), and finally, a script requiring the second module.

Init Module

local Initiation = {}
Initiation.__index = Initiation 
Initiation.RandomVariable = "https://devforum.roblox.com/t/why-is-the-index-there/2823310"

Act Module

local Activation = {}
setmetatable(Activation, Initiation)
Activation.RandomFunction = function(self, ...)
    -- Anything you want goes here.
end

Global Script

local Module = require(game:GetService("ReplicatedStorage"):WaitForChild("Module_2")
print(Initiation.RandomVariable) -- I got & printed the variable from the first module, but it was never defined in the second module.

Keep in mind this is only one way to utilize __index, and that I also did this from memory… lol.

1 Like

__index is a magical keyword

A lot of people throw around terms like “metamethod” and “setmetatable” but for someone unfamiliar with OOP and any of those terms, those explanations mean nothing.

In lua there’s “magical” words like and, or, for, that do special things. __index is just another one of those keywords, just named in a weird way, and also can only be used in an odd place.

The meta in metatable means “middle”. This gives a clue into what it was originally designed for. Imagine you are on a trail:


WATERFALL <-------- [MAP] ---------> POND
                                       ^
                                 you are here

There is a sign with a map in the middle of this trail. The path on the left goes to a waterfall, the path on the right goes to a pond.

Let’s say you are currently at the pond and want to go to the waterfall. You look all around the pond and can’t find the waterfall. Then you remembered seeing a sign with a map somewhere along the trail. So you walk back to the middle of the trail and take a look at the sign with the map.


WATERFALL <-------- [MAP] ---------> POND
                      ^
                 you are here

Ah! The map says you have to take the left trail to get to the waterfall. So you take that path and arrive happily at the waterfall.

The map in this case is the meta- (middle) -table. (middle of the waterfall and pond)

You are at the pond.

pond = {
  ["rocks"] = 9
  ["trees"] = 30
}

You look around the pond for the waterfall:

print(pond.waterfall) --> nil

It’s not there! But you remembered seeing a map earlier, before arriving at the pond

map = { ? }
setmetatable(pond, map)

Upon arriving back at the map, you take a good look at its keys.

waterfall_area = {
  ["waterfall"] = "Found it!"
}

map = {
  ["__index"] = waterfall_area
}

Looks like this special keyword “__index” is set to equal waterfall_area! Maybe if I follow this __index, it will take me to that waterfall area.

print(pond.waterfall) --> Found it!

Now what’s really going on? When you run lua code, the machine attempts to do whatever you told it to do. When you make a for loop that repeats 10 times, the machine will repeat whatever is in that loop 10 times.
Metatables work similarly, by hinting to the machine what to do, or more specifically, where to look.

By setting the metatable of pond to be the table map, when the “waterfall” index cannot be found, the machine will walk back to the map and look for the special magic word: __index
If it finds this special magical word (spelled exactly like so with two underscores), and this key is set to the name of another table waterfall_area, it will now go to the waterfall_area table, and look inside for the key “waterfall”.
It finds it and now can print the corresponding value → “Found it!”

The same process occurs if you were trying to call a function pond.TakeCoolPicture() but the functon cannot be found in pond. The machine will check to see if waterfall_area has a TakeCoolPicture() function, and call that instead.

But wait, how come map and waterfall_area are two different tables, but in the original example, the magical keyword “__index” in Car was referring to itself? Well metatables can indeed be a separate “middle” table, but in practice, most people just combine the metatable(map) and the destination table(waterfall_area) into one.

By doing:

setmetatable(pond, waterfall_area)
waterfall_area.__index = waterfall area

The machine, unable to find the waterfall, can now check its metatable, which is the waterfall_area, find the magical keyword “__index”, see that it is set to waterfall_area, and now head off to look inside the waterfall_area for the “waterfall” key!!!

I know it sounds redundant and the analogy is falling apart, but that’s exactly what it does. Just due to the quirk of how metatables work, you have to do the extra step of telling the machine to “go to where its already at”

All of this is somewhat of a simplification, and there’s other magical keywords ooo referred to as “metamethods” that can be placed in a metatable to do cool things. They are all string keys that start with exactly two underscores __
Why? I don’t know. It just is that way. Examples: __newindex __add _eq

But the most important one is just __index. If there’s a function I want to be able to call from ten different Parrot, Eagle, Hawk, etc… tables:
I can just make one single screech function, and place it in my bird metatable. Now if I set the metatable for each one of these Parrot, Eagle, etc to be bird, I’d be able to call
Eagle.screech(), Parrot.screech(), etc from any one of them. This shows the principle of inheritance, the basis of OOP. There’s more to it, but hopefully this post helped you clear up some confusion.

3 Likes