Skip to content
VLSI Mentor

Perl Part I · Module 7 · References

Copying Nested Data: What Gets Shared

Two earlier chapters deferred this question. Chapter 7.2 established that a reference refers rather than copies, and Chapter 7.5 established that [ @a ] does build a genuinely new array — but only flagged that the copy goes one level deep. This chapter settles it. There are three things you can do that all look like copying, they differ in exactly how much they share, and the difference is invisible until something gets modified. One measured run separates all three, and the middle case is the one that surprises people: a copy that faithfully reflects changes to the data inside it while ignoring changes to the structure around it. Once you can see why, the fix is one function call, and knowing when you need it is the practical skill.

Advanced17 min readPerlPart IReferencesData Structures

Part I · Module 7 · Chapter 7.13 · Copying Nested Data: What Gets Shared

1. Three Things That Look Like Copying

Azvya Education Pvt. Ltd.VLSI Mentor
copy-depths.pl
use strict;
use warnings;
use Storable qw(dclone);

my $orig = { rows => [ [1, 2], [3, 4] ], name => "top" };

my $alias   = $orig;
my $shallow = { %$orig };
my $deep    = dclone($orig);

$orig->{rows}[0][0] = 999;
$orig->{name}       = "changed";

printf "%-9s rows[0][0]=%-4s name=%s\n", "original", $orig->{rows}[0][0],    $orig->{name};
printf "%-9s rows[0][0]=%-4s name=%s\n", "alias",    $alias->{rows}[0][0],   $alias->{name};
printf "%-9s rows[0][0]=%-4s name=%s\n", "shallow",  $shallow->{rows}[0][0], $shallow->{name};
printf "%-9s rows[0][0]=%-4s name=%s\n", "deep",     $deep->{rows}[0][0],    $deep->{name};
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
original  rows[0][0]=999  name=changed
alias     rows[0][0]=999  name=changed
shallow   rows[0][0]=999  name=top
deep      rows[0][0]=1    name=top

Two mutations were applied to $orig: one deep inside it (rows[0][0]) and one at the top level (name). Read the three copies against those.

  • $alias = $orig followed both. It is not a copy at all — it is a second scalar holding the same reference, which is Chapter 7.2 §2's rule.
  • $shallow = { %$orig } followed the inner change and not the outer one. name stayed top; rows[0][0] became 999.
  • $deep = dclone($orig) followed neither. rows[0][0] is still 1.

That shallow row is the whole chapter. It is the only one of the three whose behaviour depends on where the change was, and it is why "did you copy it?" is not a sufficient question.

The three depths, drawn

Three ways to copy, and how much each one shares with the original

Diagram comparing an alias, a shallow copy and a deep copy by how much of the original structure each shares$origrows and name$alias = $origsame referenceShares allouter and inner{ %$orig }shallow copyShares innernew outer hashdclone($orig)deep copyShares nothingfully independent12
An alias shares the whole structure. A shallow copy builds a new outer container holding the original's values — and since those values are references, the inner containers are shared. A deep copy rebuilds every level, so nothing is shared at any depth.

2. Why the Shallow Copy Behaves That Way

There is no special rule here. It falls out of two facts already established.

{ %$orig } puts %$orig in list context, which flattens the hash to its keys and values — ("rows", <a reference>, "name", "top") — and builds a new hash from that list. Chapter 7.5 §2 is where that mechanism was measured.

So the new hash holds a copy of the value for each key. And for rows, the value is a reference. Copying a reference copies an address, as Chapter 7.1 §5 showed. Both hashes now hold the same address, so both reach the same inner array.

name held a plain string, and copying a plain string copies the string. That is why the two keys behaved differently: it depends entirely on whether the value was a reference.

Stated as a rule: a shallow copy copies one level. Values that are references stay shared; values that are plain data become independent.

Corroborated from the other direction — modifying through the copy rather than the original, measured in Chapter 7.5 §2:

Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — push on the copy, then write through it
after pushing onto the copy : @rows has 2, copy has 3
after $copy->[0][0] = 999   : $rows[0][0] is 999

The outer container took the push independently; the inner one took the write jointly. Same rule, seen from the copy's side.

3. Deep Copy With dclone

Azvya Education Pvt. Ltd.VLSI Mentor
The one-line fix
use Storable qw(dclone);

my $deep = dclone($orig);

Storable is core Perl — version 3.23 on the 5.34.1 used here — so there is nothing to install, which matters on a machine where you cannot install anything. dclone takes a reference and returns a reference to a complete, independent rebuild of everything under it, to any depth.

Two limitations worth knowing before you rely on it:

  • dclone requires a reference. dclone($scalar) on a plain value dies with Not a reference, exit 2. Pass \$scalar — which works and returns a SCALAR reference — or just copy the value.
  • It cannot clone code references. A structure containing sub { ... } — a dispatch table from Chapter 7.11, say — dies with Can't store CODE items, exit 2.
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output — STDERR, the two failure modes
dclone($s)              ->  Not a reference at -e line 1.
dclone({ fn => sub{1} })->  Can't store CODE items at -e line 1.

Both are fatal rather than silently wrong, which is the right behaviour — but worth knowing before a dispatch table finds its way into a structure you clone.

Writing it by hand

For a structure whose shape you know, an explicit copy is clear and avoids the dependency question entirely:

Azvya Education Pvt. Ltd.VLSI Mentor
manual-deep.pl
use strict;
use warnings;

my $orig = { rows => [ [1, 2], [3, 4] ], name => "top" };

my $copy = {
    name => $orig->{name},
    rows => [ map { [ @$_ ] } @{ $orig->{rows} } ],
};

$orig->{rows}[0][0] = 999;

printf "original : %d\n", $orig->{rows}[0][0];
printf "manual   : %d\n", $copy->{rows}[0][0];
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
original : 999
manual   : 1

[ map { [ @$_ ] } @{ $orig->{rows} } ] is the line to read: for each inner row, [ @$_ ] builds a new array from its values, and the outer [ ] collects those into a new outer array. One [ ] per level is the whole technique.

This is worth doing when the structure is small and its shape is fixed, because it is explicit about what gets copied. For anything deeper than two levels, or whose shape varies, dclone is less error-prone — a hand-written copy that misses one level produces exactly the shallow-copy surprise this chapter is about.

4. Checking Whether Two Handles Share

When you are not sure whether something is shared, ask directly. Chapter 7.2 §2 established that == on two references compares locations.

Azvya Education Pvt. Ltd.VLSI Mentor
shares.pl
use strict;
use warnings;
use Storable qw(dclone);

my $orig    = { rows => [ [1, 2] ] };
my $alias   = $orig;
my $shallow = { %$orig };
my $deep    = dclone($orig);

printf "alias   : outer %s  inner %s\n",
    ($alias   == $orig            ? "same" : "different"),
    ($alias->{rows} == $orig->{rows} ? "same" : "different");
printf "shallow : outer %s  inner %s\n",
    ($shallow == $orig            ? "same" : "different"),
    ($shallow->{rows} == $orig->{rows} ? "same" : "different");
printf "deep    : outer %s  inner %s\n",
    ($deep    == $orig            ? "same" : "different"),
    ($deep->{rows} == $orig->{rows} ? "same" : "different");
Azvya Education Pvt. Ltd.VLSI Mentor
Captured output
alias   : outer same  inner same
shallow : outer different  inner same
deep    : outer different  inner different

Three distinct signatures, and they match §1's behaviour exactly. shallow is different at the outer level and same at the inner — which is the sharing that produced the surprising row.

This is the fastest way to settle "is this actually a copy" without having to mutate anything, and it is worth putting in a test when a function's contract says it does not modify its argument.

5. Why This Matters in Engineering Work

A subroutine that takes a structure and "copies it for safety" may not have. my %local = %$arg; looks defensive and shares every nested value. If the subroutine then sorts a row in place or normalises a field, the caller's data changes. That is a real bug shape, and the fix is either dclone or being honest in the function's name about modifying in place.

Baselines need deep copies. Parsing a report, keeping a pristine copy, applying a transformation and diffing the two only works if the pristine copy is genuinely independent. A shallow copy makes the "before" track the "after" at depth, and the diff comes out empty — which reads as "the transformation did nothing" rather than "my baseline was wrong".

Shallow is right for option merging. my %opts = (%defaults, %user); is the standard idiom and wants exactly shallow semantics.

And the cost is real at scale. dclone on a large parsed structure is not free. Use it where the independence is needed, not as a blanket habit.

6. Common Mistakes

Thinking my $b = $a copies. It is a second handle on the same structure.

Thinking { %$a } copies the whole thing. It copies one level; nested references stay shared.

Testing a copy by modifying the top level. That cannot distinguish shallow from deep. Modify the deepest value.

A hand-written copy that misses a level. Independent where you looked, shared where you did not.

dclone on a plain value. It needs a reference.

dclone on a structure containing code references. It cannot clone those.

Reaching for dclone by default. It rebuilds everything; use it where independence is required.

7. Exercise

Exercise 1 — the four handles. Reproduce §1 exactly: an alias, a shallow copy and a dclone, then modify one deep value and one top-level value of the original. Print all four rows and confirm you get the same four-row pattern.

Exercise 2 — the distinguishing row. From Exercise 1's output, name the one row that behaves differently depending on where the change was, and explain in two sentences why name and rows behaved differently in it.

Exercise 3 — from the copy's side. Starting from a shallow copy, push a row onto the copy and confirm the original's row count is unchanged. Then write to $copy->[0][0] and confirm the original changed.

Exercise 4 — the sharing check. Reproduce §4 and report the three signatures. Then add a fourth handle made with [ map { [ @$_ ] } ... ] and report its signature.

Exercise 5 — manual deep copy. Write §3's manual copy, modify the original's deepest cell, and confirm the copy is unchanged. Then remove one level of [ ] and show the copy now follows the change.

Exercise 6 — testing at depth. For the one-level-short copy from Exercise 5, write three checks: add a key, change a top-level value, and change a deep value. Report which checks pass and which catches the bug.

Exercise 7 — dclone limits. Call dclone on a plain scalar and record the error. Then build a structure containing sub { 1 } and dclone it, and record what happens.

Exercise 8 — option merging. Build a %defaults hash and a %user hash with one overlapping key, merge them with my %opts = (%defaults, %user);, and print the result. Say in one sentence why shallow semantics are correct here and whether dclone would change anything.

8. Summary

  • Three things look like copying and share different amounts. Measured: an alias follows every change, a shallow copy follows inner changes only, a deep copy follows none.
  • my $b = $a is not a copy — it is a second scalar holding the same reference.
  • { %$a } and [ @$a ] copy one level. Values that are references stay shared; plain values become independent.
  • That is why two keys of the same shallow copy can behave differently — it depends on whether the value was a reference.
  • dclone from Storable is a full deep copy. Core Perl, version 3.23 here. It needs a reference and cannot clone code references.
  • A manual deep copy is one [ ] or { } per level — [ map { [ @$_ ] } @{ $x->{rows} } ]. Missing one level produces exactly the shallow surprise.
  • Test a copy by modifying its deepest value. Top-level tests cannot tell shallow from deep.
  • == on two references tells you whether they share, level by level, without mutating anything.
  • Shallow is the right default — option merging, adding keys, deliberate sharing. Deep costs time proportional to the whole structure; use it when independence is required.

Chapter 7.14 closes the module with the function that has been quietly used throughout it: ref, what it returns for each kind of reference, and how to use it to validate a structure's shape before trusting it.

Where this fits

Part of the Perl curriculum.