There are two major ways indicating failure in a function. One of them is to throw (or raise) an exception by calling
die
, croak
, or some other method. The other one is to return a false value. In Perl this false values is usually an undef
.
Some people claim that throwing an exception is a better way to indicate error, but in case you (or the people who wrote the code-base) have
decided to return undef
then the question remains:
How to return undef
?
Actually returning undef
from a function is simple, but due to the context sensitivity of perl it has several gotchas.
We just have to decide which one to avoid and which one to fall in.
Return undef explicitly (and what's wrong with that)
The solution that seems to be obvious to many people, especially who are new to Perl, is to explicitly return undef
by writing:
return undef;
. This is what we try in the following example. We have a function called div
that will attempt to divide two
numbers, but if the denominator is 0, we must signal an error as we cannot divide by 0. In that case we call return undef;
We call the div
function 4 times. Twice we assign the value to a scalar variable and twice we assign it to an array.
Then we check if the result is true. For a scalar we use (if (defined $x)
),
but for arrays, we usually check truth by checking if the array is empty (if (@x_results)
).
examples/explicit_return_undef.pl
use 5.010;
use strict;
use warnings;
sub div {
my ($x, $y) = @_;
if ($y !=0 ) {
return $x/$y;
}
return undef;
}
my $x = div(6, 2);
if (defined $x) {
say "Success! The results is $x";
} else {
say "Failure! We received undef";
}
my $y = div(42, 0);
if (defined $y) {
say "Success! The results is $y";
} else {
say "Failure! We received undef";
}
my @x_results = div(6, 2);
if (@x_results) {
say "Success! We can divide 6 by 2";
} else {
say "Failure!";
}
my @y_results = div(42, 0);
if (@y_results) {
say "Success! We can divide 42 by 0";
} else {
say "Failure!";
}
The result looks like this:
Success! The results is 3
Failure! We received undef
Success! We can divide 6 by 2
Success! We can divide 42 by 0
The first 3 results are correct, but then in the 4th row we see an incorrect result. This code now thinks that perl can divide by 0.
The problem is that our function returned undef
which got assigned to the @y_results
array which means the content
of the array became a single undef
. As if we wrote @y_results = (undef);
. A one-element array is not empty,
even if that element is undef
, and thus if (@y_results)
returned true.
So let's try another solution.
Return undef implicitly
The only thing we changed is that now, in case of error, we call return;
without explicitly returning undef
examples/implicit_return_undef.pl
use 5.010;
use strict;
use warnings;
sub div {
my ($x, $y) = @_;
if ($y !=0 ) {
return $x/$y;
}
return;
}
my $x = div(6, 2);
if (defined $x) {
say "Success! The results is $x";
} else {
say "Failure! We received undef";
}
my $y = div(42, 0);
if (defined $y) {
say "Success! The results is $y";
} else {
say "Failure! We received undef";
}
my @x_results = div(6, 2);
if (@x_results) {
say "Success! We can divide 6 by 2";
} else {
say "Failure!";
}
my @y_results = div(42, 0);
if (@y_results) {
say "Success! We can divide 42 by 0";
} else {
say "Failure!";
}
This time the result is correct:
Success! The results is 3
Failure! We received undef
Success! We can divide 6 by 2
Failure!
This happens because the parameter-less return
has the magic feature that in
scalar context
it returns undef
, but in list context
it returns an empty list ()
.
Sounds perfect.
It is not.
Prohibit Explicit Return Undef
Before showing the problem with this solution though, let's see how can we avoid the first problem.
How can we make sure that we don't have explicit return undef;
in our code?
Because this issue was part of the original Perl Best Practices book of Damian Conway, Perl::Critic has a policy against it called Subroutines::ProhibitExplicitReturnUndef.
If, following the advice to check one policy at a time we run the next command:
perlcritic --single-policy Subroutines::ProhibitExplicitReturnUndef examples/explicit_return_undef.pl
we will get a report:
"return" statement with explicit "undef" at line 11, column 5. See page 199 of PBP. (Severity: 5)
Using this policy in our setup (e.g. in the .perlcriticrc
file), will help us locate the places where undef
was
returned explicitely, and it will make sure we get notified if some adds such code to our code-based.
When implicit return breaks our code
I promised to show when the second solution, the implicit return of undef
, by calling a simple return;
will break our code.
First let's see a code snippet using explicit return undef using return undef;
:
examples/explicit_return_undef_hash.pl
use 5.010;
use strict;
use warnings;
use Data::Dumper qw(Dumper);
sub div {
my ($x, $y) = @_;
if ($y !=0 ) {
return $x/$y;
}
return undef;
}
my %results = (
'42/0' => div(42, 0),
'6/2' => div(6, 2),
);
print Dumper \%results;
The result is
$VAR1 = {
'42/0' => undef,
'6/2' => '3'
};
what we expected.
Now lets see the same code but with implicit return undef using return;
examples/implicit_return_undef_hash.pl
use 5.010;
use strict;
use warnings;
use Data::Dumper qw(Dumper);
sub div {
my ($x, $y) = @_;
if ($y !=0 ) {
return $x/$y;
}
return;
}
my %results = (
'42/0' => div(42, 0),
'6/2' => div(6, 2),
);
print Dumper \%results;
The result is really strange:
$VAR1 = {
'42/0' => '6/2',
'3' => undef
};
How did '3' become a key and '6/2' a value in this hash?
The only clue we might get is the Odd number of elements in hash assignment ... warning.
The problem here is that we basically have
'42/0' => ,
'6/2' => 3,
which is the same as
'42/0', , '6/2', 3,
In the first row we don't have a value and perl disregards that place where we have two comma one after the other. Which means perl actually sees this:
'42/0' => '6/2',
3 => ,
So we have 3 elements in the hash assignment (an odd number) and perl fills in the missing last value with undef
.
This happens because in this case the div
calls were in list context and the function returned an
empty list.
So the same thing that helped us earlier, that the empty return;
gives an empty list in list context, now breaks our code.
So after all this solution isn't perfect either.
Alway enforce scalar context
The user of our function can solve this by putting the call to div
into scalar context:
examples/implicit_return_undef_hash_with_scalar.pl
use 5.010;
use strict;
use warnings;
use Data::Dumper qw(Dumper);
sub div {
my ($x, $y) = @_;
if ($y !=0 ) {
return $x/$y;
}
return;
}
my %results = (
'42/0' => scalar div(42, 0),
'6/2' => scalar div(6, 2),
);
print Dumper \%results;
The result is correct then:
$VAR1 = {
'42/0' => undef,
'6/2' => '3'
};
but this means the user has to remember to put scalar
in front of the call.
It is still probably better than the explicit return undef, but it is not exactly DWIM.
Forbid list context
Seeing all this trouble, and seeing that the trouble only manifests when the function is called in list context,
as the author of the div
function, we can decide to forbid calling the function in list context.
We can use wantarray to recognize when is the function called in list context
and throw an exception (using croak
):
examples/prohibit_list_context.pl
use 5.010;
use strict;
use warnings;
use Data::Dumper qw(Dumper);
use Carp qw(croak);
sub div {
my ($x, $y) = @_;
croak 'Cannot use "div" in list context' if wantarray;
if ($y !=0 ) {
return $x/$y;
}
return;
}
my $x = div(6, 2);
print "$x\n";
my %results = (
'42/0' => scalar div(42, 0),
'6/2' => scalar div(6, 2),
);
print Dumper \%results;
my @y = div(6, 2);
The result is that we will soon find all the places where we have called the function in list context and will be forced to fix those places.
3
$VAR1 = {
'42/0' => undef,
'6/2' => '3'
};
Cannot use "div" in list context at examples/prohibit_list_context.pl line 9.
main::div(6, 2) called at examples/prohibit_list_context.pl line 20
Of course this means we cannot use the function in list context, for example we cannot write:
print "The result of 6/2 is ", div(6, 2);
but we can write
print "The result of 6/2 is ", scalar div(6, 2);
or even
print "The result of 6/2 is " . div(6, 2);