2.8 – Metatables

Every table and userdata object in Lua may have a metatable. This metatable is an ordinary Lua table that defines the behavior of the original table and userdata under certain special operations. You can change several aspects of the behavior of an object by setting specific fields in its metatable. For instance, when an object is the operand of an addition, Lua checks for a function in the field "__add" in its metatable. If it finds one, Lua calls that function to perform the addition.

We call the keys in a metatable events and the values metamethods. In the previous example, the event is "add" and the metamethod is the function that performs the addition.

You can query and change the metatable of an object through the set/getmetatable functions (see 5.1).

A metatable may control how an object behaves in arithmetic operations, order comparisons, concatenation, and indexing. A metatable can also define a function to be called when a userdata is garbage collected. For each of those operations Lua associates a specific key called an event. When Lua performs one of those operations over a table or a userdata, it checks whether that object has a metatable with the corresponding event. If so, the value associated with that key (the metamethod) controls how Lua will perform the operation.

Metatables control the operations listed next. Each operation is identified by its corresponding name. The key for each operation is a string with its name prefixed by two underscores; for instance, the key for operation “add” is the string "__add". The semantics of these operations is better explained by a Lua function describing how the interpreter executes that operation.

The code shown here in Lua is only illustrative; the real behavior is hard coded in the interpreter and it is much more efficient than this simulation. All functions used in these descriptions (rawget, tonumber, etc.) are described in 5.1. In particular, to retrieve the metamethod of a given object, we use the expression

  1. metatable(obj)[event]

This should be read as

  1. rawget(metatable(obj) or {}, event)

That is, the access to a metamethod does not invoke other metamethods, and the access to objects with no metatables does not fail (it simply results in nil).

  • “add”: the + operation.

    The function getbinhandler below defines how Lua chooses a handler for a binary operation. First, Lua tries the first operand. If its type does not define a handler for the operation, then Lua tries the second operand.

    1. function getbinhandler (op1, op2, event)
    2. return metatable(op1)[event] or metatable(op2)[event]
    3. end

    Using that function, the behavior of the op1 + op2 is

    1. function add_event (op1, op2)
    2. local o1, o2 = tonumber(op1), tonumber(op2)
    3. if o1 and o2 then -- both operands are numeric?
    4. return o1 + o2 -- `+' here is the primitive `add'
    5. else -- at least one of the operands is not numeric
    6. local h = getbinhandler(op1, op2, "__add")
    7. if h then
    8. -- call the handler with both operands
    9. return h(op1, op2)
    10. else -- no handler available: default behavior
    11. error("...")
    12. end
    13. end
    14. end
  • “sub”: the - operation. Behavior similar to the “add” operation.

  • “mul”: the * operation. Behavior similar to the “add” operation.

  • “div”: the / operation. Behavior similar to the “add” operation.

  • “pow”: the ^ (exponentiation) operation.

    1. function pow_event (op1, op2)
    2. local o1, o2 = tonumber(op1), tonumber(op2)
    3. if o1 and o2 then -- both operands are numeric?
    4. return __pow(o1, o2) -- call global `__pow'
    5. else -- at least one of the operands is not numeric
    6. local h = getbinhandler(op1, op2, "__pow")
    7. if h then
    8. -- call the handler with both operands
    9. return h(op1, op2)
    10. else -- no handler available: default behavior
    11. error("...")
    12. end
    13. end
    14. end
  • “unm”: the unary - operation.

    1. function unm_event (op)
    2. local o = tonumber(op)
    3. if o then -- operand is numeric?
    4. return -o -- `-' here is the primitive `unm'
    5. else -- the operand is not numeric.
    6. -- Try to get a handler from the operand
    7. local h = metatable(op).__unm
    8. if h then
    9. -- call the handler with the operand and nil
    10. return h(op, nil)
    11. else -- no handler available: default behavior
    12. error("...")
    13. end
    14. end
    15. end
  • “concat”: the .. (concatenation) operation.

    1. function concat_event (op1, op2)
    2. if (type(op1) == "string" or type(op1) == "number") and
    3. (type(op2) == "string" or type(op2) == "number") then
    4. return op1 .. op2 -- primitive string concatenation
    5. else
    6. local h = getbinhandler(op1, op2, "__concat")
    7. if h then
    8. return h(op1, op2)
    9. else
    10. error("...")
    11. end
    12. end
    13. end
  • “eq”: the == operation. The function getcomphandler defines how Lua chooses a metamethod for comparison operators. A metamethod only is selected when both objects being compared have the same type and the same metamethod for the selected operation.

    1. function getcomphandler (op1, op2, event)
    2. if type(op1) ~= type(op2) then return nil end
    3. local mm1 = metatable(op1)[event]
    4. local mm2 = metatable(op2)[event]
    5. if mm1 == mm2 then return mm1 else return nil end
    6. end

    The “eq” event is defined as follows:

    1. function eq_event (op1, op2)
    2. if type(op1) ~= type(op2) then -- different types?
    3. return false -- different objects
    4. end
    5. if op1 == op2 then -- primitive equal?
    6. return true -- objects are equal
    7. end
    8. -- try metamethod
    9. local h = getcomphandler(op1, op2, "__eq")
    10. if h then
    11. return h(op1, op2)
    12. else
    13. return false
    14. end
    15. end

    a ~= b is equivalent to not (a == b).

  • “lt”: the < operation.

    1. function lt_event (op1, op2)
    2. if type(op1) == "number" and type(op2) == "number" then
    3. return op1 < op2 -- numeric comparison
    4. elseif type(op1) == "string" and type(op2) == "string" then
    5. return op1 < op2 -- lexicographic comparison
    6. else
    7. local h = getcomphandler(op1, op2, "__lt")
    8. if h then
    9. return h(op1, op2)
    10. else
    11. error("...");
    12. end
    13. end
    14. end

    a > b is equivalent to b < a.

  • “le”: the <= operation.

    1. function le_event (op1, op2)
    2. if type(op1) == "number" and type(op2) == "number" then
    3. return op1 <= op2 -- numeric comparison
    4. elseif type(op1) == "string" and type(op2) == "string" then
    5. return op1 <= op2 -- lexicographic comparison
    6. else
    7. local h = getcomphandler(op1, op2, "__le")
    8. if h then
    9. return h(op1, op2)
    10. else
    11. h = getcomphandler(op1, op2, "__lt")
    12. if h then
    13. return not h(op2, op1)
    14. else
    15. error("...");
    16. end
    17. end
    18. end
    19. end

    a >= b is equivalent to b <= a. Note that, in the absence of a “le” metamethod, Lua tries the “lt”, assuming that a <= b is equivalent to not (b < a).

  • “index”: The indexing access table[key].

    1. function gettable_event (table, key)
    2. local h
    3. if type(table) == "table" then
    4. local v = rawget(table, key)
    5. if v ~= nil then return v end
    6. h = metatable(table).__index
    7. if h == nil then return nil end
    8. else
    9. h = metatable(table).__index
    10. if h == nil then
    11. error("...");
    12. end
    13. end
    14. if type(h) == "function" then
    15. return h(table, key) -- call the handler
    16. else return h[key] -- or repeat operation on it
    17. end
  • “newindex”: The indexing assignment table[key] = value.

    1. function settable_event (table, key, value)
    2. local h
    3. if type(table) == "table" then
    4. local v = rawget(table, key)
    5. if v ~= nil then rawset(table, key, value); return end
    6. h = metatable(table).__newindex
    7. if h == nil then rawset(table, key, value); return end
    8. else
    9. h = metatable(table).__newindex
    10. if h == nil then
    11. error("...");
    12. end
    13. end
    14. if type(h) == "function" then
    15. return h(table, key,value) -- call the handler
    16. else h[key] = value -- or repeat operation on it
    17. end
  • “call”: called when Lua calls a value.

    1. function function_event (func, ...)
    2. if type(func) == "function" then
    3. return func(unpack(arg)) -- primitive call
    4. else
    5. local h = metatable(func).__call
    6. if h then
    7. return h(func, unpack(arg))
    8. else
    9. error("...")
    10. end
    11. end
    12. end