EverythingRightPlace
EverythingRightPlace

Reputation: 1197

Non-defined return of sub

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

Answers (4)

RobEarl
RobEarl

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

amon
amon

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:

  1. 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)

  2. 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

Zaid
Zaid

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

mpapec
mpapec

Reputation: 50637

You can avoid temporary variable,

$a = funct() // $a;

Upvotes: 3

Related Questions