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.


13.4.4 – Tracking Table Accesses

Both __index and __newindex are relevant only when the index does not exist in the table. The only way to catch all accesses to a table is to keep it empty. So, if we want to monitor all accesses to a table, we should create a proxy for the real table. This proxy is an empty table, with proper __index and __newindex metamethods, which track all accesses and redirect them to the original table. Suppose that t is the original table we want to track. We can write something like this:

    t = {}   -- original table (created somewhere)
    
    -- keep a private access to original table
    local _t = t
    
    -- create proxy
    t = {}
    
    -- create metatable
    local mt = {
      __index = function (t,k)
        print("*access to element " .. tostring(k))
        return _t[k]   -- access the original table
      end,
    
      __newindex = function (t,k,v)
        print("*update of element " .. tostring(k) ..
                             " to " .. tostring(v))
        _t[k] = v   -- update original table
      end
    }
    setmetatable(t, mt)
This code tracks every access to t:
    > t[2] = 'hello'
    *update of element 2 to hello
    > print(t[2])
    *access to element 2
    hello
(Notice that, unfortunately, this scheme does not allow us to traverse tables. The pairs function will operate on the proxy, not on the original table.)

If we want to monitor several tables, we do not need a different metatable for each one. Instead, we can somehow associate each proxy to its original table and share a common metatable for all proxies. A simple way to associate proxies to tables is to keep the original table in a proxy's field, as long as we can be sure that this field will not be used for other means. A simple way to ensure that is to create a private key that nobody else can access. Putting these ideas together results in the following code:

    -- create private index
    local index = {}
    
    -- create metatable
    local mt = {
      __index = function (t,k)
        print("*access to element " .. tostring(k))
        return t[index][k]   -- access the original table
      end,
    
      __newindex = function (t,k,v)
        print("*update of element " .. tostring(k) ..
                             " to " .. tostring(v))
        t[index][k] = v   -- update original table
      end
    }
    
    function track (t)
      local proxy = {}
      proxy[index] = t
      setmetatable(proxy, mt)
      return proxy
    end
Now, whenever we want to monitor a table t, all we have to do is t = track(t).