SoFunction
Updated on 2025-04-13

Detailed explanation of metatatable in Lua

Metatable in Lua is an ordinary table, but it mainly has the following functions:

1. Define the behavior of arithmetic operators and relational operators
2. Provide support for the Lua function library
3. Control access to table

Metatables defines operator behavior

Metatable can be used to define the behavior of arithmetic operators and relational operators. For example: When Lua tries to add two tables, it will check in order whether one of the two tables has a metatable and whether the metatable has a __add field. If Lua checks this __add field, it will be called, and this field is called metamethod.

Each value in Lua can have a metatable (in Lua 5.0, only table and userdata can exist metatable). Each table and userdata value have its own metatable, while all values ​​of each other type share a metatable of this type. In Lua code, you can set the metatable by calling setmetable and can only set the metatable of the table. When calling the Lua C API in C/C++, you can set the metatable of all values. By default, the string type has its own metatable, while the others do not:

Copy the codeThe code is as follows:

print(getmetatable('hi')) --> table: 003C86B8
print(getmetatable(10))  --> nil

The parameters of Metamethod are operands, for example:

Copy the codeThe code is as follows:

local mt = {}
function mt.__add(a, b)
    return 'table + ' .. b
end
local t = {}
setmetatable(t, mt)
print(t + 1)

Each arithmetic operator has a corresponding metamethod:

+ __add
* __mul
- __sub
/ __div
- __unm (for negation)
% __mod
^ __pow

There is a corresponding metamethod for the connection operator: __concat

Similarly, there are corresponding metamethods for relational operators:

== __eq
< __lt
<= __le

Other relational operators are represented by the above three types:
a ~= b is expressed as not (a == b)
a > b is expressed as b < a
a >= b is expressed as b <= a

Unlike arithmetic operators, the relational operator will produce an error when comparing two values ​​with different metamethods (rather than metatables). The exception is the comparison operator. The result of the comparison of two values ​​with different metamethods is false.

It should be noted, however, that in the comparison of integer types a <= b can be converted to not (b < a), but this condition does not necessarily hold if all elements of a certain type are not properly sorted. For example: NaN (Not a Number) in a floating point number represents an undefined value, NaN <= x is always false and x < NaN is always false.

Provides support for Lua function library

The Lua library can define and use metamethod to accomplish some specific operations. A typical example is the tostring function in the Lua Base library (the print function calls this function for output) that checks and calls __tostring metamethod:

Copy the codeThe code is as follows:

local mt = {}
mt.__tostring = function(t)
    return '{' .. (t, ', ') .. '}'
end
 
local t = {1, 2, 3}
print(t)
setmetatable(t, mt)
print(t)

Another example is the setmetatable and getmetatable functions, which define and use the __metatable domain. If you want the metatable of the value you set to not be modified, you can set the __metatable field in the metatable of the value. Getmetatable will return this field, and setmetatable will generate an error:

Copy the codeThe code is as follows:

mt.__metatable = "not your business"
local t = {}
setmetatable(t, mt)
print(getmetatable(t)) --> not your business
setmetatable(t, {})
    stdin:1: cannot change protected metatable

See a complete example:

Copy the codeThe code is as follows:

Set = {}
 
local mt = {}
 
function (l)
    local set = {}
-- Set metatatable for Set
    setmetatable(set, mt)
    for _, v in ipairs(l) do set[v] = true end
    return set
end
 
function (a, b)
-- Check whether a b is all Set
    if getmetatable(a) ~= mt or getmetatable(b) ~= mt then
-- The second parameter of error is level
-- level Specifies how to get the error location
-- The level value is 1 indicates that the error position is the position where the function was called
-- The level value is 2 indicates that the error position is where the function calling error is called
        error("attempt to 'add' a set with a not-set value", 2)
    end
    local res = {}
    for k in pairs(a) do res[k] = true end
    for k in pairs(b) do res[k] = true end
    return res
end
 
function (a, b)
    local res = {}
    for k in pairs(a) do
        res[k] = b[k]
    end
    return res
end
 
mt.__add =
mt.__mul =
 
mt.__tostring = function(s)
    local l = {}
    for e in pairs(s) do
        l[#l + 1] = e
    end
    return '{' .. (l, ', ') .. '}'
end
 
mt.__le = function(a, b)
    for k in pairs(a) do
        if not b[k] then return false end
    end
    return true
end
 
mt.__lt = function(a, b)
    return a <= b and not (b <= a)
end
 
mt.__eq = function(a, b)
    return a <= b and b <= a
end
 
local s1 = ({1, 2, 3})
local s2 = ({4, 5, 6})
print(s1 + s2)
print(s1 ~= s2)

Control the access of tables

__index metamethod

When we access a non-existent domain of the table, Lua tries to call __index metamethod. __index metamethod accepts two parameters table and key:

Copy the codeThe code is as follows:

local mt = {}
mt.__index = function(table, key)
    print('table -- ' .. tostring(table))
    print('key -- ' .. key)
end
 
local t = {}
setmetatable(t, mt)
local v =

The __index field can also be a table, so Lua will try to access the corresponding domain in the __index table:

Copy the codeThe code is as follows:

local mt = {}
mt.__index = {
    a = 'Hello World'
}
 
local t = {}
setmetatable(t, mt)
print() --> Hello World

We can easily implement single inheritance through __index (similar to JavaScrpit implement single inheritance through prototype). If __index is a function, we can implement more complex functions: multiple inheritance, caching, etc. We can access the field i of table t through rawget(t, i) instead of accessing __index metamethod. Note that we do not expect to increase the access speed of tables through rawget (the overhead of calling functions in Lua is much greater than the overhead of accessing tables).

__newindex metamethod

Lua checks the __newindex metamethod when assigning a value to a non-existent domain of the table:

1. If __newindex is a function, Lua will call the function instead of assigning it
2. If __newindex is a table, Lua will assign the value to this table.

If __newindex is a function, it can accept three arguments table key value. If you want to ignore the __newindex method to assign values ​​to the table's domain, you can call rawset(t, k, v)

Combining __index and __newindex can achieve many functions, such as:


-only table
with default values

Read-only table

Copy the codeThe code is as follows:

function readOnly(t)
    local proxy = {}
    local mt = {
        __index = t,
        __newindex = function(t, k, v)
            error('attempt to update a read-only table', 2)
        end
    }
    setmetatable(proxy, mt)
    return proxy
end
 
days = readOnly{'Sun', 'Mon', 'Tues', 'Wed', 'Thur', 'Fri', 'Sat'}
print(days[1])
days[2] = 'Noday' --> stdin:1: attempt to update a read-only table

Sometimes, we need to set a unique key for the table, so we can use this trick:

Copy the codeThe code is as follows:

local key = {} -- unique key
local t = {}
t[key] = value