Generalized iteration triggers __call in metatables when __iter is absent, leaving it impossible to iterate normally

As the title says. Make a metatable with a populated table and run the generalized iterator on it. According to the Developer API, this should fire __iter. It will, but when __iter is absent, it instead fires __call, which isn’t entirely useful in the case of metatable usage, requiring me to perform checks that the table isn’t called with nil. It also means I cannot provide the generalized iterator, I have to return next, self, nil, which will cause an inescapable loop and timeout the script. next() apparently counts as a __call, or the generalized iteration does in general, causing an infinite loop. A workaround is to implement __iter anyway, but I don’t want to do that; I want to use the actual generalized iteration because I find it to be the most performant typically.

Clarification for my sake or investigating this to remove the __call altogether for generalized iteration is requested.

Example below. Place is attached too, you can just open it and see what I mean.

^ note in the above, the output is from when I uncommented the return next, t, nil. Otherwise it will do the __call output and nothing in the for loop.

bug example 4-9-2025 mewow.rbxl (55.9 KB)

System info:

CPU

Intel(R) Core(TM) i7-14700K @ 5.0GHz with a base of 3.4

Memory

32.0 GB

Disk 0 (C:)

Samsung SSD 870 EVO 1TB

GPU 1

NVIDIA GeForce RTX 4070 Ti

Driver version:	32.0.15.7260
Driver date:	2/25/2025
DirectX version:	12 (FL 12.1)
Physical location:	PCI bus 1, device 0, function 0

Utilization	1%
Dedicated GPU memory	1.8/12.0 GB
Shared GPU memory	0.1/15.9 GB
GPU Memory	1.9/27.9 GB

Expected behavior

__call should not be triggered, its supposed to be when the table is, from a lua dev’s POV, called like a function per the API wiki. Im not doing that, and thus I shouldn’t be forced to use pairs() or returning next(). Given generalized iteration is more performant. It locks me out entirely from using generalized iteration just because I’m using __call.

This is intended behavior. The __call metamethod has always acted like an iterator function since metatables were added.

This is also briefly mentioned in the Generalized Iteration RFC.

2 Likes

As @index_self said, this is by design, and was necessary in order to prevent the addition of generalized iteration from breaking existing code.

local t = setmetatable({}, {
  __call = function(self)
    if self.iterated then
      return nil
    end
    
    self.iterated = true
    return "hello!"
  end,
})

for value in t do
  print(value) -- prints `hello!`
end

ty to both, i got the clarification i wanted, though ofc I wasn’t trying to use bug reports as just a question, I just found the behavior odd given there’s __pairs, __iter, etc. it felt like an anti-pattern, but if its necessary then its necessary

1 Like

__pairs does not exist in Luau, and __iter did not exist until recently in the grand scheme of things.

1 Like

If Lua hadn’t had an iteration protocol at all, and for-in loops didn’t exist, indeed, we’d not have done it this way. But Luau evolved from Lua, and we were very selective about the sorts of things we considered breaking in doing so (namely things that had already been broken in Roblox in the interest of sandboxing). Breaking any existing code relying on callable tables as function iterators was not in the cards.

1 Like

I must’ve misremembered then abt the __pairs, but yeah again I appreciate it

yeah i understand, i just didnt realize its intentionality because it was giving me mixed signals and requires a kind of hacky response to work around it

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