@Prototrode explained the infinite chain very well. It is entered once you trigger the __index
, which wakes when an empty table element is indexed. The chain that occurs when we have __index
as a table is quite similar to C stack overflow when it’s recursively called as a function.
Comparable is __newindex
’s behavior:
local t = {}
setmetatable(t, {__newindex = function(self, k, v)
self[k] = v
end,})
t.key = "value" --> stack overflow
The infinite loop can be avoided by using rawset()
, rawget()
and rawequal()
, which don’t invoke the metamethods. In normal indexing of an empty element, the C code returns 0, which is later translated to nil
. Prior to returning, the interpreter automatically looks for __index
.
setmetatable()
requires two tables, the table and its metatable. The latter must include at least a single metamethod in order to have an effect.
For easier understanding, it helps to write the tables separately.
local t = {}
local mt = {} -- a normal table at this point
setmetatable(t, mt) -- set as a metatable, no functionality
mt.__index = t -- routing access to the table t
print(t.key) --> infinite chain again
As we can see, __index
is a key in the metatable. And any attempt to directly read the property results in a recursion Prototrode explained.
print(getmetatable(t)) --> mt (containing __index)
To avoid the inconvenient error altogether, since there’s always a risk of attempting to read a non-existent property, we can use a step standardly taken in OOP.
local t = {}
t.__index = t
print(getmetatable(t)) --> nil
print(t.key) --> nil
Now the table t
is routing field access to itself and contains __index
as an element. From my limited knowledge of internal handling of metatables, it seems the metatable refers to a second table as a prerequisite for separating methods from metamethods, and hence getmetatable(t)
doesn’t return anything.
In OOP, a single table is a common pattern and pointing to itself a good foundation to be a metatable to the object (self
). However, setmetatable(t, {__index = t})
works as well, because the class functionality is accessed from self
and not the class table.