Rants about Lua

I've started using Lua some months ago, while looking for a more powerful way to configure awesome. At this time, around March 2008, Lua seemed to be the best language to integrate inside the core system of awesome.

I still think that Lua was a good choice, but after 8 months, it shows some important drawbacks.

I'll try to keep my explanation simple and to make you understand everything, even if you do not know Lua.

I refer here to Lua version 5.1.

Design flaws

Length operator

Lua has a length operator on its objects, known as #. It can be used to get the size of various objects.

> return #"lol"
> 3

This operator works for table, string, etc… It's possible to define this operator by setting a __len meta-method on a userdata value.

The problem is that you cannot redefine it on string or table objects, see:

> a = { "hello", "world" }
> return #a
2
> setmetatable(a, { __len = function () return 18 end })
> return #a
2

Indeed, looking at the Lua core code:

      case OP_LEN: {
        const TValue *rb = RB(i);
        switch (ttype(rb)) {
          case LUA_TTABLE: {
            setnvalue(ra, cast_num(luaH_getn(hvalue(rb))));
            break;
          }
          case LUA_TSTRING: {
            setnvalue(ra, cast_num(tsvalue(rb)->len));
            break;
          }
          default: {  /* try metamethod */
            Protect(
              if (!call_binTM(L, rb, luaO_nilobject, ra, TM_LEN))
                luaG_typeerror(L, rb, "get length of");
            )
          }
        }
        continue;
      }

You clearly see that tables and strings always use the internal length operator, never the __len meta-method.

That's for me a design problem, which will cause more trouble. We'll see later.

index and newindex metamethods

Lua defines two useful meta-methods, which are __index and __newindex. Both can be set on a table or any other object. __index will be called upon each read access to an undefined key on an object, and __newindex upon each write access.

> a = {}
> -- function are not defined, this is just an example
> setmetatable(a, { __index = myindexfunction, __newindex = mynewindexfunction })
> a[1] = "hello" -- This will call __newindex metamethod
> return a[2] -- This will call __index metamethods
> return a[1] -- This will NOT call __index

The last line does not call __index meta-method because a[1] does exists. This is a problem when you want to use table as object, because sometimes you want to monitor access to the table elements.

This can be easily worked around using a proxy system: you don't store
things in the table you manipulate, but in another table.

> a = {}
> realtable = {}
> -- function are not defined, this is just an example:
> setmetatable(a, { __index = myindexfunction, __newindex = mynewindexfunction })

Where meta-methods are something like:

function myindexfunction(table, key)
   return realtable[key]
end

function mynewindexfunction(table, key, value)
   realtable[key] = value
end

This way, our a table will always be empty, and realtable will have the data. At every read or write access to a, the meta-methods will be called. This is very convenient and widely used hack.

But this has serious drawbacks: as we saw before, the length operator (#) cannot be redefined on a table. That means #a will always be 0, and you cannot get the table length anymore, except by defining another method or a special attribute.

Also, Lua has several functions in the table library that are used to manipulate table in a easy way. The problem is that standard functions like table.insert or table.remove do raw accesses to the table. Meaning that if you do table.insert(a, 1, 1) it will insert the value 1 at the key 1 into a, without calling the __newindex meta-methods, breaking all your beautiful object-oriented model.

Another solution is to use a userdata object, like done in the Lua newproxy function (which is under-documented).

The problem is that it breaks all the other functions that are waiting for a table as argument, because they see a userdata, not a table. So this time table.insert is now more usable, which somehow fixes the problem, but not in the right way IMHO. However, this allows to use the __len meta-method.

Development model

The development model of Lua is, from my point of view, non-existent.

There is no public version control system repository available, so there's no chance to really contribute to Lua. It seems only a defined set of people work on it and therefore, the development system is very closed to my eyes, in comparison of usual projects.

Conclusion

I still think Lua is a good choice, because it is very easy to integrate into any C program, and to expand to fulfill your needs. However, some bad design choices were made, and the poor and closed development model chosen does not allow to have a good overview of the future of Lua.

This has been well stated by the authors themselves.