Removing Metatables from a table in Lua
Asked Answered
D

1

5

I want to "unhook" a metatable from a table and was wondering if:

tbl = setmetatable(tbl, false) -- or nil

is the correct way to do this? I could not find any info about how to do it correctly. Do I need to use an assignment operator?

Also, would this be enough to destroy the metatable attached to the table if the metatable never had a reference and was anonymous?:

tbl = setmetatable({}, {__index = something})
-- later on:
tbl = nil

and the garbage collector would be enough to remove both tables?

Didi answered 9/9, 2016 at 10:46 Comment(2)
nil should be fine, it won't accept false.Ronald
lua.org/manual/5.3/manual.html, lua.org/cgi-bin/demoSensory
S
9

According to the Lua reference, which you should always consult befor putting up a question here, setmetatable(tbl, nil) will delete the metatable of table tbl unless tbl's original metatable is protected. Or let's better say it does not delete the metatable but the reference to it. The table that served as metatable will of course not be deleted as long as there are other references to it.

Befor you ask people if a simple function call works, try it yourself. You can use https://www.lua.org/cgi-bin/demo or any other Lua interpreter and you get your answer in seconds without involving anyone else.

Running this code:

setmetatable({}, false)

or

setmetatable({})

will result in

input:1: bad argument #2 to 'setmetatable' (nil or table expected)

Now you know that you cannot enter false and you have to enter nil explicitly.

To checkout that __metatable thing you would have read in the reference manual you could then try this code

local tbl = setmetatable({}, {__metatable = true})
setmetatable(tbl, nil)

Which results in the following output:

input:2: cannot change a protected metatable

To the second part of your question:

tbl = nil will not delte the table referenced by tbl. It will only remove the reference tbl to it.

local a = {}
local b = a
b = nil
print(a)

a is still a table. You only removed one of its references.

Once there is no reference left the garbage collector may collect the table.

setmetatable(tbl, {}) will establish a reference to the table returned by the table constructor {} and store that reference somewhere in the guts of tbl.

If tbl was the last reference to that table it will be collected as garbage at some point. Then of course the only reference to the table you set as metatable will also be gone and it will removed as well.

If you do something like that:

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

, a = nil will not delete b's metatable

So yes it will remove both tables if no other reference to either one of them is left.

Sensory answered 9/9, 2016 at 12:36 Comment(1)
It's worth pointing out that if the developer did protect the metatable by setting __metatable, you can still get at it if debug.getmetatable exists (or if you're able to load in the debug library yourself) - Otherwise you'd have to do it via the C APISad

© 2022 - 2024 — McMap. All rights reserved.