Many people seem to be getting confused by metatables and metamethods, so I’m going to write a guide on them.
So what exactly is the difference between a metatable and a regular table? Well, there isn’t one. Any table can act as a metatable for itself or for other tables. The actual usage of a metatable by the language comes into play when you use setmetatable
and getmetatable
. If a table has a metatable defined, then you can alter how it works (mostly via operator overloading) using metamethods.
Some metamethods are only called under certain circumstances, an example would be the _index
metamethod when the thing indexed was nil. How this works is when the language tries to index a table using a key and there’s nothing there, it then looks for an __index metamethod, and if it finds one, it hands over control to it.
local MT = {}
function MT.__index(self, key)
return 0
end
local t = setmetatable({}, MT)
print(t.something) --> 0
So right here, the value can’t be found and so the return value of __index
is used instead, and so it returns 0 rather than nil as it normally would. I’ll give another example:
local MT = {}
function MT.__call(self, ...)
return self[math.random(1, #self)]
end
local t = {"Hello", "Good", "Sir", "How", "Are", "You"}
setmetatable(t, MT)
for i = 1, 15 do
print(t())
end
--> You Sir Are Are You Good Sir Are Good How Sir How Sir How You
The __call
metamethod lets us put a method in place for when someone attempts to call the table. Normally this would cause an error because tables are tables and not functions, but in this case, it’s possible thanks to metatables.
If you wish to grab a value without setting off any metamethods in a table, use rawget(t, key)
, likewise use rawset(t, key, value)
for setting without provoking a metatable.
There’s also about more 17 metamethods, but it’d take me ages to go over all of them, so I’ll link you to them here.