Passing by
Assignments by copy or reference?
When assigning, perl
copies by value, see the following code:
my $var1 = "not modified";
my $var2 = $var1; # Copy the content, not the variable itself
$var1 = "modified";
print "$var2\n";
This code will print the initial non-modified value:
not modified
It is because the assignment copies the content of the variable and does not alias the variable.
To inspect the identity of variables, we should inspect their references.
A word about references
A reference represents a scalar value that holds where-the-value-is-stored information (sort of).
Having the same locations means it is the same variable (or an alias).
The string representation of a reference even gives details about what kind of value (scalar, array, hash…) is pointed. Like the SCALAR(...)
just seen below:
SCALAR(0x...)
If you’re only interested by the type pointed, you can get this information (e.g. for type checking) from the ref
operator:
my @array = ();
print ref(\@array) . "\n";
That will give you:
ARRAY
You can get (create) a reference by using the backslash operator and dereference with sigils (e.g. $
) or with ->
my $str = "foo";
# Get reference
my $ref = \$str;
# Dereference with $
print "$$ref\n";
Or
my @array = ( "foo", "bar", "baz" );
# Get reference
my $ref = \@array;
# Derefrence with ->
print "$ref->[0]\n";
Both of them will print:
foo
You can also build a reference by its name with symbolic references:
my $name = "var";
# Store in $var
$$name = "bazinga";
print "$var\n";
That will print:
bazinga
Or you can use “anonymous” operators []
or {}
like this:
my @characters1 = ( "Sheldon", "Leonard", "Penny" );
my @characters2 = ( "Howard", "Rajesh", "Bernadette", "Amy" );
my @big_bang_theory = ();
push @big_bang_theory, [@characters1];
push @big_bang_theory, [@characters2];
This way, array references are pushed but it’s references to new arrays (hence it breaks the link between @characters1
/@characters2
and @big_bang_theory
content).
Or you can even obtain a reference to a reference and later dereference multiple times. It enables the possibility to produce weird/dumb things like this:
my $str = "bazinga";
# Get a ref on a ref on a ref on a...
my $rrrrrrrrrref = \\\\\\\\\\$str;
# De-de-de-...-de-dereference
print "$$$$$$$$$$$rrrrrrrrrref\n";
Or
my $str = "bazinga";
# De-de-de-...-de-dereference a re-re-re-re-...-ref
print $$$$$$$$$${\\\\\\\\\\$str} . "\n";
But don’t do this.
Inspect references
Back to our variables, you can check their “identity” by printing their reference:
# Compare storage location
print \$var1 . " vs " . \$var2 . "\n";
That gives 2 different storage locations:
SCALAR(0x55589c6378e0) vs SCALAR(0x55589c6378f8)
^^ ^^
So it clearly states that these are 2 different variables.
But wait, there are also some cases where the assignment is actually aliasing, it is what happen in foreach
loops:
my @array = ( "foo", "bar", "baz" );
foreach my $var (@array) {
$var = "modified"; # Modify the initial @array
}
print "@array\n";
And the array is then well modified:
modified modified modified
While we are in the topic of “inspecting references”, you can use a module like Devel::Peek or similar to get internal details on references (or any variable). You will get more details than what you ever wanted!
Call by reference or by value?
We discussed assignments but how are arguments passed to subs?
Answer: they are always passed “by reference” but it would be better to say “aliased” to avoid any confusion with the concept of references discussed earlier.
This fact is far from obvious, since as you will see in the next example, retrieving arguments into new variables (hence assigning by copy) will often make you believe that Perl is “calling by value”.
Modifications of variables into subs
Inside subs, depending on the way you get/use arguments, editing the variables won’t usually affect the arguments.
sub bycopy {
my $cp = shift; # /!\ copy is done here (not during call) from aliased argument(s)
# my $cp = (@_); # Same with this
$cp = "modified";
}
my $var = "not modified";
bycopy($var);
print "$var\n";
That prints:
not modified
The modification is not visible outside the sub because in order to propagate the change, it should be returned to caller with a return $cp;
and assigned with $var = bycopy($var)
(actually even the return is optional because of “default variable magic”).
Obviously, this limitation is usually a good idea (think side effect) but not if you wanted to modify the variable in the caller scope intentionally.
To be able to modify the variable inside the sub so that it will be propagated to the caller, I can use a reference:
sub byref {
my $ref = shift;
$$ref = "modified";
}
my $variable = "not modified";
byref(\$variable);
print "$variable\n";
That prints:
modified
This way, with some gymnastics, I pass the variable itself via its reference, whatever it is (scalar, array, hash…), I can edit it and it will persist.
But there is an easier and obvious way, the default array @_
is actually aliased!
sub bydefaultarray {
$_[0] = "modified";
}
my $variable = "not modified";
bydefaultarray($variable);
print "$variable\n";
And it is well modified:
modified
You only have to take care to work directly on @_
(or aliases made with care) and do not use a function like shift
or any assignment that will give you only a copy.
Implicit conversion to reference with prototype
Then there is another thing to know around subs and references: the usage of prototype. Prototypes change the way perl behaves and it allows for instance a sub to receive an array and to implicitely convert it to a reference.
The following example forces the array to its reference instead of flattening it:
sub proto(\@) {
my $ref = shift; # It is the array ref, not the first item of @array
print "$ref\n";
$ref->[0] = "dog";
$ref->[1] = "cat";
$ref->[2] = "pig";
}
my @array = ("foo", "bar", "baz");
# Array flattening won't occur but instead a reference will be passed
proto(@array); # proto(\@array);
print "@array\n";
With the output:
ARRAY(0x55ac34f261c0)
dog cat pig
Aliasing
There are ways to alias variables without systematic reconstruction from reference. For instance, you can use the module Data::Alias:
use Data::Alias;
my $variable = "not modified";
# Alias!
alias $alias = $variable;
$variable = "modified";
print "$alias\n";
And it prints well:
modified
Recent Perl also comes with some aliasing abilities (without having to deal with symbol table):
use feature qw/refaliasing declared_refs/;
no warnings qw/experimental::refaliasing experimental::declared_refs/;
my $variable = "not modified";
my \$alias = \$variable; # Assigning to reference
$variable = "modified";
print "$alias\n";
That will print:
modified
And printing the reference values to compare:
print \$alias . " equals " . \$variable . "\n";
And there are well pointing to the same thing:
SCALAR(0x5637ffc833a0) equals SCALAR(0x5637ffc833a0)
Aliasing is also a matter of performances, and these tricks were heavily used for this purpose.
Recent Perl comes with Copy-On-Write feature, see perl 5.20.0 performance enhancements and perlguts COW
Thanks to COW, when assigning, we get 2 different variables but the value is actually copied only if needed and it could produce significant performance gain.
Tags
Thibault Duponchelle
Thibault Duponchelle is a software developer. Mainly interested in GNU/Linux, Open Source, Perl, C and Assembly.
Browse their articles
Feedback
Something wrong with this article? Help us out by opening an issue or pull request on GitHub