Search code examples
recursionlualua-table

lua oop deep copy a table


My deep copy code:

function deepcopy(orig)
local orig_type = type(orig)
local copy
if orig_type == 'table' then
    copy = {}
    for orig_key, orig_value in next, orig, nil do
        copy[deepcopy(orig_key)] = deepcopy(orig_value)
    end
    setmetatable(copy, deepcopy(getmetatable(orig)))
else -- number, string, boolean, etc
    copy = orig
end
return copy
end

I'm trying to implement this to oop using self, but couldn't get it to work, here is what I've tried so far

function block:deepcopy()
local orig_type = type(self)
local copy
if orig_type == 'table' then
    copy = {}
   for orig_key, orig_value in next, self, nil do
       copy[self:deepcopy(orig_key)] = deepcopy(orig_value)
   end
   setmetatable(copy, self:deeepcopy(getmetatable(self)))
else
    copy = orig
end
return copy

Solution

  • In the OOP version of the function, self:deepcopy(something) with the method syntax (colon) doesn't do what you want it to. It is equivalent to self.deepcopy(self, something); the second argument something is ignored and you just end up trying to re-copy the same self over and over until there's a stack overflow. You have to do self.deepcopy(something) with a dot to pass something as the self argument (the argument that is copied).

    Calling self.deepcopy inside the definition of the deepcopy method assumes that every subtable has a self.deepcopy function. If not, you will get an "attempt to call a nil value" error. But you could do this if you want every subtable to have its own version of deepcopy that is used when copying the immediate children of that table (keys, values, metatable). For instance, you could have a subtable whose deepcopy method does not copy the metatable. Here is the basic version where the subtable has the same deepcopy method:

    local block = {}
    
    function block:deepcopy()
        if type(self) == 'table' then
            local copy = {}
            for key, value in pairs(self) do
                copy[self.deepcopy(key)] = self.deepcopy(value)
            end
            return setmetatable(copy, self.deepcopy(getmetatable(self)))
        else
            return self
        end
    end
    
    block.a = { a = 10, deepcopy = block.deepcopy }
    block:deepcopy() -- works
    
    block.a = { a = 10 }
    block:deepcopy() -- error: "attempt to call a nil value (field 'deepcopy')"
    

    But you don't need to rewrite the function at all to use it in object-oriented style. Try using your first definition of deepcopy. Do object.deepcopy = deepcopy, and then call object:deepcopy() and you will get a copy of the object.