Search code examples
lualuajitmetatable

luajit how to automatically set default metatable to every newly created table?


I have a personal project and a pure lua writed object module which provides metatable with methods filter map etc to a table , I don't want to require and setmetatable for every line local foo={}


Solution

  • Global Constructor

    I have a personal project and a pure lua writed object module which provides metatable with methods filter, map etc to a table , I don't want to require and setmetatable for every line local foo={}.

    You can avoid the need for require by just ensuring that your "object module" is always loaded first, setting global variables. I consider require to be cleaner however as it makes clear the dependencies of your files (and does not pollute the global environment).

    To avoid the need for setmetatable, you can write yourself a constructor function:

    local metatable = ...
    function Table(t) -- NOTE: global function
        return setmetatable(t, metatable)
    end
    

    then, in another file which you ensure only runs after this file was executed:

    local foo = Table{}
    

    You might want to shorten Table to T if you use this very often.

    Setting a metatable for all (new) tables

    Do you really want this?

    First of all: You probably do not want local t = {} to set a metatable on t. This would mess with linters like Luacheck while also making your code hard to follow for everyone familiar with Lua but unfamiliar with this hack.

    Setting a metatable with __index also interferes with the usage of tables as dictionaries / hash maps; users now need to use rawget / rawset to circumvent your metatable. Consider the following snippet:

    local dict = { filter = "bar" }
    print(dict.filter) -- "bar", as expected
    print(dict.map) -- filter function - unexpected
    print(rawget(dict, "map")) -- nil, as expected
    

    It will also harm performance of every table access. Do you really want this just for some syntactic sugar?

    Furthermore, if you heavily set metamethods (such as the arithmetic metamethods) even if it doesn't really make sense, you again get unexpected behavior by allowing passing tables where numbers are expected etc. Lua's partial strictness when dealing with incompatible types is what distinguishes it from JS.

    How to do it

    how to automatically set default metatable to every newly created table?

    This is not possible in general; the proper way to set metatables is to explicitly use constructors.

    debug.setmetatable, primitives & functions

    Using debug.setmetatable, you can set "shared"/"common" metatables for all primitive types (boolean, number, string) as well as functions. You can not set a shared metatable for objects this way however.

    Hooking global/environmental variable access

    This is what koyaanisqatsi's snippet does: It catches global variable access and sets the metatable on all new table global variables. This is insufficient as it forces you to use global/environmental variables, which is both bad for performance and code quality. It will in particular not work at all for local variables such as the local foo = {} in your example. It will also not work for temporary variables in expressions (consider ({...}):filter(...)).

    Debug Hooks

    The following approach would be more reliable than hooking environmental variable access:

    • Set a debug hook that runs after every instruction.
    • Iterate over locals & upvalues and set the metatable for each table; perhaps remember old/new locals/upvalues/tables.
    • Eventually consider doing this recursively, for structures such as {{}}.

    Obviously this would be awfully slow. It is very likely that there still exist many "edge cases" this doesn't catch (what about table creation in C, for instance?).

    Forking Lua

    The only proper solution would be to add such a feature - a default metatable for all tables - to the language by forking it and implementing this right in the function where Lua creates new tables. This is the only way this could be implemented adequately - that is, with adequate performance & reliability.