Previous Page
Next Page

9.12. Returning Failure

Use a bare return to return failure.

Notice that each final return statement in the examples of the previous guideline used a return keyword with no argument, rather than a more-explicit return undef.

Normally, relying on default behaviour is not best practice. But in the case of a return statement, relying on the default return value actually prevents a particularly nasty bug.

The problem with returning an explicit return undef is thatcontrary to most people's expectationsa returned undef isn't always false.

Consider a simple subroutine like this:

    use Contextual::Return;

    sub guesstimate {
        my ($criterion) = @_;

        my @estimates;
        my $failed = 0;

        # [Acquire data for specified criterion]

        return undef if $failed;

        # [Do guesswork based on the acquired data]

        # Return all guesses in list context or average guess in scalar context...
        return (
            LIST   { @estimates                  }
            SCALAR { sum(@estimates)/@estimates; }
        );
    }

The successful return values are both fine, and completely appropriate for the two contexts in which the subroutine might be called. But the failure value is a serious problem. Since guesstimate( ) specifically tests for calls in list context, it's obvious that the subroutine is expected to be called in list contexts:


    if (my @melt_rates = guesstimate('polar melting')) {
        my $model = Std::Climate::Model->new({ polar_melting => \@melt_rates });

        for my $interval (1,2,5,10,50,100,500) {
            print $model->predict({ year => $interval })
        }
    }

But if the guesstimate( ) subroutine fails, it returns a single scalar value: undef. And in a list context (such as the assignment to @melt_rates), that single scalar undef value becomes a one-element list: (undef). So @melt_rates is assigned that one-element list and then evaluated in the overall scalar context of the if condition. And in scalar context an array always evaluates to the number of elements in it, in this case 1. Which is true.

Oops![*]

[*] And here "Oops!" means: the if block executes despite the failure of guesstimate( ) to acquire any meaningful data. So, when the climate model requests a numerical polar melting rate, that undef is silently converted to zero. This dwimmery causes the model to show that polar melting rates have absolutely no connection to world climate in general, and to rising ocean levels in particular. So mankind can happily keep burning fossil fuels at an ever-greater rate, secure in the knowledge that it has no effect. Until one day, the only person left is Kevin Costner. On a raft.

What should have happened, of course, is that guesstimate( ) should have returned a failure value that was false in whatever context it was called, i.e., undef in scalar context and an empty list in list context:

        if ($failed) {
            return (
                LIST   { (  )    }
                SCALAR { undef }
            );
        }

But that's precisely what a return itself does when it's not given an argument: it returns whatever the appropriate false value is for the current call context. So, by always using a bare return to return a "failure value", you can ensure that you will never bring about the destruction of the entire planetary ecosystem because of an expectedly true undef.

Meanwhile, Chapter 13 presents a deeper discussion on the most appropriate ways to propagate failure from a subroutine.

    Previous Page
    Next Page