Reputation: 71
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
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
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.
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;
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