Reputation: 1197
How can I avoid that a variable gets touched by a return of a subfunction. I wrote following code snippet
#!/usr/bin/perl
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
#$var = 1;
return $var if defined $var;
}
my $tmp = funct;
$a = $tmp if defined $tmp;
print "$a\n";
and I don't want the value of $a
be changed from it's initial init
if $var
isn't defined in the subfunction.
Where is the error or is there a better way to solve this problem?
Greetings
Upvotes: 0
Views: 116
Reputation: 7912
It looks like you're trying to detect error scenarios by returning undef
. In addition to @amon's suggestions, you could die
out of funct
when an error occurs:
#!/usr/bin/perl
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
...
if ($something_went_wrong) {
die 'something bad';
}
...
return $var;
}
eval {
$a = funct;
1;
} or do {
# log/handle the error in $@
};
print "$a\n";
Upvotes: 0
Reputation: 57590
The return $foo if $bar
is equivalent to $bar and return $foo
. Therefore, when $bar
is false, then this statement evaluates to the value of $bar
. As subroutines return the value of the last executed statement, your sub returns either $var
if it is defined, or a false value, if not.
You can either go the explicit route, and put another return there:
# all branches covered
return $var if defined $var;
return;
This is silly, and equivalent to return $var
.
Now the false value that your sub returns is in fact defined, so it is assigned to $a
. You could test for truth instead:
$a = $tmp if $tmp;
… but that opens another can of worms.
Return values are extraordinary bad at telling us whether they are the wanted return value, or an error indicator. There are two clean ways around that:
Return a second value that tells us whether the function exited OK:
sub func {
my $var;
return (1, $var) if defined $var;
return (0); # not strictly needed
}
my ($ok, $tmp) = func();
$a = $tmp if $ok;
(basically, the comma-ok idiom as seen in Golang)
Return a container that has to be destructured to obtain the actual return value. A possibility is to either return undef
(or something false) on error, or a scalar reference to the value when such a value exists:
sub func {
my $var;
return \$var if defined $var;
return undef; # actually not needed
}
my $tmp = func();
$a = $$tmp if defined $tmp;
(basically, a Maybe
type as seen in Haskell)
Here is a way to use that without obious temp vars:
($a) = map { $_ ? $$_ : () } func(), \$a;
Upvotes: 3
Reputation: 37136
Running this program in the Perl debugger shows that $tmp
is set to an empty string, which evaluates to true with the defined
function. This is why the conditional that sets $a
evaluates to true:
$ perl -d
Loading DB routines from perl5db.pl version 1.33
Editor support available.
Enter h or `h h' for help, or `perldoc perldebug' for more help.
use strict;
use warnings;
my $a = "init";
sub funct{
my $var;
#$var = 1;
return $var if defined $var;
}
my $tmp = funct;
$a = $tmp if defined $tmp;
print "$a\n";
__END__
main::(-:4): my $a = "init";
DB<1> x $a
0 undef
DB<2> n
main::(-:12): my $tmp = funct;
DB<2> x $a
0 'init'
DB<3> x $tmp
0 undef
DB<4> n
main::(-:14): $a = $tmp if defined $tmp;
DB<4> x $tmp
0 ''
To fix, simply return $var
, without the if defined $var
. This will set $tmp
to undef
Upvotes: 1