This first edition was written for Lua 5.0. While still largely relevant for later versions, there are some differences.
The fourth edition targets Lua 5.3 and is available at Amazon and other bookstores.
By buying the book, you also help to support the Lua project.


28.1 – Userdata

Our first concern is how to represent array values in Lua. Lua provides a basic type specifically for this: userdata. A userdatum offers a raw memory area with no predefined operations in Lua.

The Lua API offers the following function to create a userdatum:

    void *lua_newuserdata (lua_State *L, size_t size);
The lua_newuserdata function allocates a block of memory with the given size, pushes the corresponding userdatum on the stack, and returns the block address. If for some reason you need to allocate memory by other means, it is very easy to create a userdatum with the size of a pointer and to store there a pointer to the real memory block. We will see examples of this technique in the next chapter.

Using lua_newuserdata, the function that creates new arrays is as follows:

    static int newarray (lua_State *L) {
      int n = luaL_checkint(L, 1);
      size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
      NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
      a->size = n;
      return 1;  /* new userdatum is already on the stack */
    }
(The luaL_checkint function is a variant of luaL_checknumber for integers.) Once newarray is registered in Lua, you can create new arrays with a statement like a = array.new(1000).

To store an entry, we will use a call like array.set(array, index, value). Later we will see how to use metatables to support the more conventional syntax array[index] = value. For both notations, the underlying function is the same. It assumes that indices start at 1, as is usual in Lua.

    static int setarray (lua_State *L) {
      NumArray *a = (NumArray *)lua_touserdata(L, 1);
      int index = luaL_checkint(L, 2);
      double value = luaL_checknumber(L, 3);
    
      luaL_argcheck(L, a != NULL, 1, "`array' expected");
    
      luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                       "index out of range");
    
      a->values[index-1] = value;
      return 0;
    }
The luaL_argcheck function checks a given condition, raising an error if necessary. So, if we call setarray with a bad argument, we get an elucidative error message:
    array.set(a, 11, 0)
    --> stdin:1: bad argument #1 to `set' (`array' expected)

The next function retrieves an entry:

    static int getarray (lua_State *L) {
      NumArray *a = (NumArray *)lua_touserdata(L, 1);
      int index = luaL_checkint(L, 2);
    
      luaL_argcheck(L, a != NULL, 1, "`array' expected");
    
      luaL_argcheck(L, 1 <= index && index <= a->size, 2,
                       "index out of range");
    
      lua_pushnumber(L, a->values[index-1]);
      return 1;
    }
We define another function to retrieve the size of an array:
    static int getsize (lua_State *L) {
      NumArray *a = (NumArray *)lua_touserdata(L, 1);
      luaL_argcheck(L, a != NULL, 1, "`array' expected");
      lua_pushnumber(L, a->size);
      return 1;
    }
Finally, we need some extra code to initialize our library:
    static const struct luaL_reg arraylib [] = {
      {"new", newarray},
      {"set", setarray},
      {"get", getarray},
      {"size", getsize},
      {NULL, NULL}
    };
    
    int luaopen_array (lua_State *L) {
      luaL_openlib(L, "array", arraylib, 0);
      return 1;
    }
Again, we use luaL_openlib, from the auxiliary library. It creates a table with the given name ("array", in our example) and fills it with the pairs name-function specified by the array arraylib.

After opening the library, we are ready to use our new type in Lua:

    a = array.new(1000)
    print(a)               --> userdata: 0x8064d48
    print(array.size(a))   --> 1000
    for i=1,1000 do
      array.set(a, i, 1/i)
    end
    print(array.get(a, 10))  --> 0.1

Running this implementation on a Pentium/Linux, an array with 100K elements takes 800 KB of memory, as expected; an equivalent Lua table needs more than 1.5 MB.