Lua Tables — Deep & Shallow Copies
Introduction
Copying a Lua table using the assignment operator doesn’t copy anything! All you get is another pointer to the same memory block.
We can illustrate that using the following example:
local original = {a = 'alpha', b = 'beta'}
1local copy = original
2copy.a = 'GAMMA'
3print(original.a)
- 1
-
“Copy” the
original
table using a simple assignment. - 2
-
Alter the
copy
. - 3
-
Check the
original
and get the output,GAMMA
!
This shows that original
and copy
refers to the same memory block.
table.clone
& table.copy
If you have imported the lulu.table
module as
require 'lulu.table'
Then, you can make copies of any Lua table using the following two methods:
table.clone(tbl)
Returns a shallow copy of tbl
. It does not copy any metatable.
table.copy(tbl)
Returns a deep copy of tbl,
including metatables.
Shallow vs. Deep
The clone
and copy
methods create a new empty table.
A shallow clone
then inserts copies of the top-level elements from tbl
into that new table.
On the other hand, a deep copy
copies all the elements from the tbl
argument by recursively visiting all sub-tables, sub-sub-tables, and on.
The methods give identical results for arguments without sub-tables. |
Example: Simple Tables
1local orig = {a = 'alpha', b = 'beta'}
2local deep = table.copy(orig)
3local shallow = table.clone(orig)
4("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow)
putln
5deep.a = "GAMMA"
("After setting `deep.a to '%s'`", deep.a)
putln("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow)
putln
6shallow.b= "RHO"
("After setting `shallow. to '%s'`", shallow.b)
putln("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow) putln
- 1
-
Here,
orig
is a simple Lua table with two key, value pairs and no sub-tables. - 2
-
deep
is a deep copy oforig
, so we expect changes todeep
not to changeorig
. - 3
-
shallow
is a shallow clone oforig, so changes to shallow might also affect
orig`. - 4
- The examples on this page use Scribe for formatted printing.
- 5
-
Change to
deep
will only be reflected indeep
no matter the structure oforig
. - 6
-
Because
orig
has no sub-tables, a change toshallow
is only reflected inshallow
.
When you run the program, you get:
Output: Simple Tables
original = {['a'] = "alpha", ['b'] = "beta"}
deep copy = {['a'] = "alpha", ['b'] = "beta"}
shallow copy = {['a'] = "alpha", ['b'] = "beta"}
After setting `deep.a to 'GAMMA'`
original = {['a'] = "alpha", ['b'] = "beta"}1
deep copy = {['a'] = "GAMMA", ['b'] = "beta"}
shallow copy = {['a'] = "alpha", ['b'] = "beta"}
After setting `shallow. to 'RHO'`
original = {['a'] = "alpha", ['b'] = "beta"}
deep copy = {['a'] = "GAMMA", ['b'] = "beta"}2 shallow copy = {['a'] = "alpha", ['b'] = "RHO"}
- 1
-
Only
deep
is changed. - 2
-
Only
shallow
is changed.
In this example, the original table had no sub-tables, so there is no difference between deep copying and shallow cloning.
The results differ when we alter the example by putting the table values into sub-tables!
Example: Table with Sub-Tables
1local orig = {a = {'alpha'}, b = {'beta'}}
local deep = table.copy(orig)
local shallow = table.clone(orig)
("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow)
putln
deep.a[1] = "GAMMA"
2("After setting `deep.a[1] to '%s'`", deep.a[1])
putln("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow)
putln
shallow.b[1]= "RHO"
3("After setting `shallow.b[1] to '%s'`", shallow.b[1])
putln("original = %t", orig)
putln("deep copy = %t", deep)
putln("shallow copy = %t", shallow) putln
- 1
-
Here, we have spuriously introduced sub-tables instead of setting
orig.a = "alpha"
we haveorig.a[1] = "alpha"
etc. - 2
-
Deep copies are independent, so this change will only affect
deep
. - 3
-
Shallow clones are only independent at the top level so this change will affect both shallow and orig. Of course, it will not affect the independent
deep
.
Now, when you run the program, you get:
Output: Table with Sub-Tables
original = {['a'] = {"alpha"}, ['b'] = {"beta"}}
deep copy = {['a'] = {"alpha"}, ['b'] = {"beta"}}
shallow copy = {['a'] = {"alpha"}, ['b'] = {"beta"}}
After setting `deep.a[1] to 'GAMMA'`
original = {['a'] = {"alpha"}, ['b'] = {"beta"}}1
deep copy = {['a'] = {"GAMMA"}, ['b'] = {"beta"}}
shallow copy = {['a'] = {"alpha"}, ['b'] = {"beta"}}
After setting `shallow.b[1] to 'RHO'`
original = {['a'] = {"alpha"}, ['b'] = {"RHO"}}
deep copy = {['a'] = {"GAMMA"}, ['b'] = {"beta"}}2 shallow copy = {['a'] = {"alpha"}, ['b'] = {"RHO"}}
- 1
-
As expected, a change to a sub-element is only seen
deep
. - 2
- In contrast, a change to a sub-element is seen in the shallow copy and the original table.
The only native complex type in Lua is a table so every non-trivial data structure will have tables with sub-tables. For this reason, it is essential to understand the distinction between having multiple references by assignment, shallow cloning, and deep copying. |