Table.foreach producing weird errors when yielding in the second argument

I’m not quite sure if this is a bug or not, but seeing as I can’t post in the bugs category I’ve elected to post here. If this is a bug, I ask that it be moved to the appropriate category.

Recently I was messing around with table.foreach for a game I was making when I stumbled across this error

2019-01-21_22-35-32

Upon further testing, I found out that the cause of it was a yield in a function I was passing to table.foreach

Code:

2019-01-21_22-28-13

If it is, in fact, a bug, this can be reproduced easily. Simply go into Studio and insert a LocalScript, copy/paste the code in the image and run a playtest. The fix itself is fairly easy, instead of using table.foreach you use a for loop like so:

2019-01-21_22-32-13

Now obviously this isn’t a massive inconvenience, but I was wondering if anybody in the community knew why this error arose. Based on the error I’m assuming that the second argument in table.foreach is a C function (I can assume this because I don’t use any metamethods in the example provided), but why would this specific function be different from the rest of the library?

Any thoughts are appreciated!

1 Like

table.foreach is implemented in C, that’s why it’s deprecated. As the error says, you can’t yield across a C function call.

The table.foreach would need to call the function for every element in the table - so it’s going to constantly be going into that function. Because of that, it can’t yield, causing the error. I’d assume the other methods are fine because they don’t have the same functionality; they don’t need to make calls to Lua in the process of the C call, so they can yield around that.

Isn’t that true for every Lua function?

No. The for in construct is implemented natively in the VM. Here’s the source of foreach to illustrate:

static int foreach (lua_State *L) {
  luaL_checktype(L, 1, LUA_TTABLE); //Checks the argument types
  luaL_checktype(L, 2, LUA_TFUNCTION);
  lua_pushnil(L);  /* first key */
  while (lua_next(L, 1)) { //While there's a next key
    lua_pushvalue(L, 2);  /* function */ //Push the following into the stack
    lua_pushvalue(L, -3);  /* key */ 
    lua_pushvalue(L, -3);  /* value */
    lua_call(L, 2, 1); //Call the function with the value
    if (!lua_isnil(L, -1))
      return 1;
    lua_pop(L, 2);  /* remove value and result */
  }
  return 0;
}

As you can see the logic itself is done inside a while loop, with hard coded calls to the table values so it’s obvious why you can’t yield: Lua can’t interfere with while loop in the C code (lua_call is not resumable)

Don’t use table.foreach. Alternatives: Numeric and generic for loop.

Numeric:

for i = 1,#tbl do
   local value = tbl[i]
end

Generic:

for i,value in pairs(tbl) do
   
end

Never use ipairs. It’s ridiculously slow. If you ever find yourself in a position where you have to use it, then you’ve designed your table structure poorly.

1 Like

Ah that makes sense, and explains why it throws the error about calling across the C boundary. Thanks!

Numeric loops are the planned alternative, I was just curious as to the reasoning behind the error

2 Likes

Off-topic, but I’d like to address this. If you were promoted by the system as a New Member, you undoubtedly also received various guidelines to abide by. It is very explicitly stated in the rules and the New Member FAQ the process in which you must use to post a Bug Report.

Good that you didn’t though, because this isn’t a bug.

2 Likes