27.1 - Array Manipulation

"Array", in Lua, is just a name for a table used in a specific way. We can manipulate arrays using the same functions we use to manipulate tables, namely lua_settable and lua_gettable. However, contrary to the general philosophy of Lua, economy and simplicity, the API provides special functions for array manipulation. The reason for that is performance: Frequently we have an array access operation inside the inner loop of an algorithm (e.g., sorting), so that any performance gain in this operation can have a big impact on the overall performance of the function.

The functions that the API provides for array manipulation are

    void lua_rawgeti (lua_State *L, int index, int key);
    void lua_rawseti (lua_State *L, int index, int key);
The description of lua_rawgeti and lua_rawseti is a little confusing, as it involves two indices: index refers to where the table is in the stack; key refers to where the element is in the table. The call lua_rawgeti(L, t, key) is equivalent to the sequence
    lua_pushnumber(L, key);
    lua_rawget(L, t);
when t is positive (otherwise, you must compensate for the new item in the stack). The call lua_rawseti(L, t, key) (again for t positive) is equivalent to
    lua_pushnumber(L, key);
    lua_insert(L, -2);  /* put `key' below previous value */
    lua_rawset(L, t);
Note that both functions use raw operations. They are faster and, anyway, tables used as arrays seldom use metamethods.

As a concrete example of the use of these functions, we could rewrite the loop body from our previous l_dir function from

        lua_pushnumber(L, i++);  /* key */
        lua_pushstring(L, entry->d_name);  /* value */
        lua_settable(L, -3);
to
        lua_pushstring(L, entry->d_name);  /* value */
        lua_rawseti(L, -2, i++);  /* set table at key `i' */

As a more complete example, the following code implements the map function: It applies a given function to all elements of an array, replacing each element by the result of the call.

    int l_map (lua_State *L) {
      int i, n;
    
      /* 1st argument must be a table (t) */
      luaL_checktype(L, 1, LUA_TTABLE);
    
      /* 2nd argument must be a function (f) */
      luaL_checktype(L, 2, LUA_TFUNCTION);
    
      n = luaL_getn(L, 1);  /* get size of table */
    
      for (i=1; i<=n; i++) {
        lua_pushvalue(L, 2);   /* push f */
        lua_rawgeti(L, 1, i);  /* push t[i] */
        lua_call(L, 1, 1);     /* call f(t[i]) */
        lua_rawseti(L, 1, i);  /* t[i] = result */
      }
    
      return 0;  /* no results */
    }
This example introduces three new functions. The luaL_checktype function (from lauxlib.h) ensures that a given argument has a given type; otherwise, it raises an error. The luaL_getn function gets the size of the array at the given index (table.getn calls luaL_getn to do its job). The lua_call function does an unprotected call. It is similar to lua_pcall, but in case of errors it throws the error, instead of returning an error code. When you are writing the main code in an application, you should not use lua_call, because you want to catch any errors. When you are writing functions, however, it is usually a good idea to use lua_call; if there is an error, just leave it to someone that cares about it.