luac-api

Lua: generic getters in C API


I am writing a Lua (5.4) extension for a C library I wrote. One thing I would like to do is to define an object-like table and use direct key access to get data for some functions that don't need any arguments other than the object table itself.

I would like to do this using a generic getter function instead of repeating the same logic for multiple attributes. The generic getter should be triggered only if the key accessed is not set as a normal Lua value, and return nil if the key is not registered as a getter function.

E.g., if I wrote this in pure Lua, I would do something along these lines:

-- Getters registry
local getters = {
    name = function(t) return "NAME: " .. math.random(1, 10) end,
    description = function(t) return "DESC: " .. math.random(11, 20) end,
}

mt = {["__index"] = function(t, k)
    if getters[k] then return getters[k](t) end
    end
}
a = {}
setmetatable (a, mt)

The random function is obviously contrived, it would be replaced in C by something that looks for userdata.

In the C API it looks like I can set functions with luaL_setfuncs() and static values with lua_setfield(), but if I push a getter function with either method, I would get the function reference, not the result of the function call.

How to write a generic getter in the C API?


Solution

  • Generally, I wouldn't use a for loop to compare strings one after the other at run time. Even if it's in C, it's eventually going to be slow for nothing if the attributes are accessed frequently. Why don't reuse Lua tables for something they are already very good at ?

    int yourAttrGetter (lua_State* L) {
      // Error checking removed for brievity
      const char* name = lua_tostring(L, 2); // key accessed
      // If the key exists in metatable, return it directly, we assume it's a method or default value
      if (luaL_getmetafield(L, 1, name)) return 1; 
      // Otherwise, build the name of a getter method and if it exists, call it
      char buf[64] = { 0 };
      snprintf(buf, 63, "get%s", name);
      if (luaL_getmetafield(L, 1, buf)) {
        lua_pushvalue(L, 1); // push self
        lua_call(L, 1, 1); // call the getter
        return 1; // result of the getter
      }
      return 0;   // nil
    }
    
    int luaopen_yourlib (lua_State* L) {
      luaL_newmetatable(L, "yourlib");
      lua_pushcfunction(L, &yourAttrGetter);
      lua_setfield(L, -2, "__index");
      // register your other getters here using lua_pushcfunction and lua_setfield
      return 1;
    }
    

    EDIT: I updated the code to give a possible solution supporting both methods and attributes. It works as follows when writing object.attr in lua:

    1. Look for "attr" in metatable. IF it exists, then it is returned directly. This allows "attr" to be used as a method or a default value, i.e. object:attr(...)
    2. If there is no "attr" in metatable, try "getattr".
    3. If "getattr" exists, call it as it was a getter method
    4. If none exists, return nil

    The advantage of my approach is that you can access attributes both in a direct style (òbject.attr) and by a getter method style (object:getattr()`). You can of course do the same for setters in __newindex, and/or use a more sophisticated approach to find a getter by trying different names (get_attr, getAttr, isattr or isAttr, etc.). However, it's probably not the most optimized solution. I suggest you to have a look at well known binders/wrappers implementation, such as SOL2 or LUAA (they are both in C++, not C), to see how they do it, certainly better than me.