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:
print(getmetatable('hi')) --> table: 003C86B8
print(getmetatable(10)) --> nil
The parameters of Metamethod are operands, for example:
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:
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:
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:
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:
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:
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
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:
local key = {} -- unique key
local t = {}
t[key] = value