Scripting HelpersARCHIVE
A little piece of Roblox history. Preserved for everyone, one question at a time.Read about the archive →
✓ ANSWER ACCEPTED

How do metatables function and how are they actually created?

Today I was having an interesting conversation/debate with a member of the discord. The topic was where metatables are actually created. The other person in this conversation argued that a metatable is created when the table is created like so:

local meta = {
	__index = function() return "nothing here to see" end
} -- he debated that this was a metatable already
local t = {}
setmetatable(t, meta) -- and I debated that if this line of code never happens meta is not a metatable

After discussing for a while, he clarified that it was because of the metamethods that he thought it was a metatable so I did this:

local meta = {
	"hello"
} -- he debated that this was a metatable already
local t = {}
setmetatable(t, meta) -- and I debated that if this line of code never happens meta is not a metatable

and asked if meta is still a metatable before setmetatable. He argued that because "setmetatable(tbl, mt) does not change the behavior of 'mt' at all..." mt is already a metatable. He then asked with the following code:

local mt = {
__index = function() return "index not found" end
}

setmetatable({}, mt)

"^metatable?? which argument is setmetatable affecting, the first one or the second?" I then replied with this code:

local mt = {
__index = function() return "index not found" end
}
-- not metatable yet
setmetatable({}, mt)
--metatable now

After reading, he asked, "So you're saying that a table with metamethods needs to actively be attached to a table for it to be considered a metatable?" I replied with yes. He then asked, "Can you show me what makes you think that? because i am not opposed to saying "you're right" but i do not think that you are right. setmetatable({}, mt) which argument does this affect? {} or mt?" I replied with, "the first argument is the table that the metatable is attatched to" and "a metatable is a table that is by definition the metatable of another table so you cannot have a metatable unless it is connected to a table and what a metatable contains certainly does not affect that it is or is not a metatable." and he said, "but right now youre just acting like i should believe you where does this imply that {} is not a metatable but setmetatable({}, {}) is" so I said, "are you telling me that without ever using setmetatable, a table I call metatable is a metatable? because that is wrong. It is just a table that is my point" In response he wrote this code and statement:

local x = {
__index = function() return "not found" end
}
setmetatable({}, x}[1] -> "not found"

"Nothing inherently changes in the x table, but the {} now has instructions for what to do (in this specific case) if a nil value is indexed." So I provided this code in response:

local a = {}
local b = {}
setmetatable(a, b)

would work and

local a = {}
local b = {}
setmetatable(b, a)
-- would work

commenting that "so neither a nor b is a metatable at the beginning no matter what is in them they are only a metatable if I use setmetatable" He then provided some code that really sparked my curiousity:

local meta = {
    __index = function(self, index) return index + 5 end
}

local t = {}

setmetatable(t, meta)

meta = nil

print(t[3]) -- this printed 8

After extensive testing someone reminded me that meta = nil would not change the elements in the table. So, I wrote this code instead:

local meta = {
    "hello"
}

local t = {}

setmetatable(t, meta)

for i,v in pairs(meta) do
    print(type(v))
    meta[i] = nil
end
print(#meta)
print(getmetatable(t)[1])  -- it worked and printed nil

At this point I was pretty confused about the whole deal. Was I wrong and he right? Was I right and he wrong? Were we both wrong? After discussing further he tested this code:

local meta = {
    __index = {[3] = "hi"}
}

local t = {}

setmetatable(t, meta)

for i,v in pairs(meta) do
    meta[i] = nil
end

print(setmetatable({}, getmetatable(t))[3]) -- printed nil

and he said, "this prints nil so that means im wrong on the fact that it doesnt run off of the original metatable" which confuses me. How does that prove he was wrong? After even further discussion we decided to ask a question so that the experts could answer it. He said that "the basis of the question should be does doing setmetatable(arg1, arg2) inherently make arg2 a metatable, or does it remain an ordinary table? (assume that arg1 and arg2 are both tables)." Anyway, I hope you, the reader, will be able to understand and help. I would really appreciate a decent explanation of how metatables actually function. Thanks!

9 comments

6

this is a great question. upvote this, people

0

oh crap, I actually wanted to ask this question like a year ago but thought it would get downvoted LOL

0

the heck 8 upvotes

0

Good question.

0

Thanks :D

0

I'm in support with incapaz; this question needs more upvotes. This's a gem.

0

" "this prints nil so that means im wrong on the fact that it doesnt run off of the original metatable" which confuses me. How does that prove he was wrong?" I said I was wrong because I had previously (incorrectly) stated that when you use setmetatable(arg1, arg2), it creates new functions for arg1 based off of arg2, rather than controlling it all from the metatable.

0

I found an article at roblox wiki that may help: http://wiki.roblox.com/index.php?title=User:Anaminus/metatables_nutshell

0

Sorry for any unclear wording or phases or even missing paragraphs. I was trying to concicely represent everything that went on in the discussion and obviously was unable to do that perfectly.

1 answer

✓ Accepted answer

There are a few pieces of confusion in this question.

"Is x a metatable?

You ask "Is x a metatable?" However, this isn't really a meaningful question.

Any table may be used as a metatable of another table.

Each and every table has the opportunity to point to one other table and declare that other table to be its metatable.

Any table may the metatable of any metatable. Multiple tables can have the same metatable. A metatable of a table may in turn have its own metatable. A table may be its own metatable.

You can get x's metatable using getmetatable(x).

You can change x's metatable to become m by calling setmetatable(x, m). setmetatable does not inspect either of its arguments (more than verifying that they are both tables). It just sets the pointer of x, so that subsequently m is x's metatable.

Some versions of your question that are more answerable:

  • Is m the metatable of x?
  • Is x the metatable of some table?
    • A "yes/no" answer is not always easy, because that table may no longer be reachable (or only be reachable from weak references!)
  • Might m be useful as a metatable? == Does it assign handlers for any metamethods?
    • This is not answerable in general because m may in turn have a metatable with a __index metamethod that produces metamethods for m!

What metatables do

The call setmetatable does very little. It only modifies the invisible metatable field on the given table.

Whenever Lua is asked to perform an operation on a table, it first tries to resolve it on its own.

For indexing t[key],

  • Lua checks if t associates a value with key. If it does, it produces that value.
  • Otherwise, it checks if t has a metatable set. If it does, it accesses getmetatable(t).__index. If this is nil, it produces nil.
  • Otherwise, it consults the __index table/function to decide what to return.

Lua performs these steps each and every time you access any table. Thus, if you modify either t or t's metatable link or the table that t has set its metatable to, subsequent accesses will use those modifications.

Roughly, this is how indexing a table works in Lua:

function index(t, key)
	local v = rawget(t, key)
	if not rawequal(v, nil) then
		return v
	end

	local m = getmetatable(t)
	if not m then
		return nil
	end

	local metamethod = m.__index
	if rawequal(metamethod, nil) then
		return nil
	elseif type(metamethod) == "table" then
		return index(metamethod, key)
	elseif type(metamethod) == "function" then
		return metamethod(t, key)
	end
	error("bad __index type `" .. type(metamethod) .. "`")
end

Code more-or-less equivalent to the above runs for each and every [] and . and : in your program.

3 comments

0

Sorry some of the phrasings in my question were not clear. I was trying to concisely represent what went on in our conversation and may have missed some elements. I am also only sixteen so my ability to write well is limited at best.

0

This is a great answer! Thank you so much!

4

Blue taslem is back!!

This discussion is preserved as it was. New answers, comments, and votes are closed.