user3410333
user3410333

Reputation: 71

How to get an actual copy of a table in Lua?

So, the following variables all refer to the same table:

x = {1,2,3}
y=x
z=y
table.remove(z,3)

Therefore the following code will output 1,2

for k,v in pairs(x) do
    print(v)
end

The internet just refers to a Lua's ability to always use variables by reference and not value.

But sometimes I want to manipulate a copy of a variable, not the original. How to do it? Why does Lua make it so hard to truly copy a variable by value and not just by reference?

Upvotes: 4

Views: 8123

Answers (2)

Nicol Bolas
Nicol Bolas

Reputation: 473174

Why does Lua make it so hard to truly copy a variable by value and not just by reference?

Because what it means to "copy" a table depends very much on what is in that table.

First, some nomenclature. You are not referencing a "variable"; you are getting a reference to a table. A "variable" is just a holder for stuff, like a number, a string, or a reference to a table.

So when you say "truly copy a variable", what you actually mean is to "copy a table." And... that's not easy.

Consider this table:

local tbl = {x = 5, y = {20}}

If you want to copy that table, do you want to have the new table's y field have a copy of the table that the old one stored? Or do you want that table to itself be a copy of the original?

Neither answer is wrong; which one you want depends entirely on what you want to do. But you can't go around blindly doing recursive copies of tables because:

local tbl = {x = 5, y = {20}}
tbl._tbl = tbl

This table now stores a reference to itself. Trying to do a blind recursive copy of that table will cause infinite recursion. You would have to detect that the table references itself and thus have the new table store a reference to the new table. And it gets even more complicated:

local tbl = {x = 5, y = {20}}
tbl.z = tbl.y

This table now has two fields that reference the same table. If you want to have a true copy of that table, then the copy needs to realize that two fields reference each other, so that when it copies the first field, it can have the second field reference the new copy rather than copying it again.

And I haven't even gotten into metatables and the gymnastics you can get up to with them. Nor does this include a discussion of things that are fundamentally non-copyable, like userdata objects from C-based APIs. If I store the result of io.open in a table, there is no mechanism for copying that file handle. So what should your copy routine do?

Lua has no default table copying API in order to make sure that you will yourself take the time to figure out how complex your copying algorithm needs to be.

Upvotes: 5

Nifim
Nifim

Reputation: 5031

A Number or String can be copied simply by assign the value to a new variable. A table on the other hand will require more work.

To copy a table in lua you need to define a copy function. 2 common types of copy functions are a shallow copy and a deep copy.

Lua-users: CopyTable

Shallow Copy:

This a simple, naive implementation. It only copies the top level value and its direct children; there is no handling of deeper children, metatables or special types such as userdata or coroutines. It is also susceptible to influence by the __pairs metamethod.

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

Deep Copy:

A deep copy copies all levels (or a specific subset of levels). Here is a simple recursive implementation that additionally handles metatables and avoid the __pairs metamethod.

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

As Nicol Bolas has stated, there are many pitfalls to copying a table. In another SO question how-do-you-copy-a-lua-table-by-value the below example was given which does cover some of the cases of concern, such as;

  • tables as keys
  • preserving metatables
  • recursive tables

Example from Tyler:

function copy(obj, seen)
    if type(obj) ~= 'table' then
        return obj 
    end
    if seen and seen[obj] then
        return seen[obj] 
    end
    local s = seen or {}
    local res = setmetatable({}, getmetatable(obj))
    s[obj] = res
    for k, v in pairs(obj) do 
        res[copy(k, s)] = copy(v, s) 
    end
    return res
end

Each of these functions have different use cases and if you are working with shallow tables like x = {1,2,3} you can do something as simple as:

x = {1,2,3}
y = {table.unpack(x)}

Upvotes: 3

Related Questions