Version 5.36 of perl was recently released. Amid the new features it contains, there is a very interesting, and maybe lesser-know one: the new defer keyword.

What does it do? It simply defers the execution of some code to the end of the enclosing block. It’s a very useful construct, because (in my opinion) it allows to make code much cleaner.

I put down a simple example which renders a checkout page for an fictional e-commerce site. There are some conditions which bring to different result, with instruction

The first version uses a classic if/else approach. Since there are functions to be called between evaluation of conditions, the if/elsif approach on a single level does not fit, so a nesting of conditional statements is needed.

sub checkout() {
    my $tplargs;

    if ( !is_user_logged() ) {
        $tplargs->{error} = 'not_logged';
    } else {
        my $user = get_user();
        my $cart = get_cart_contents();
        if ( !@$cart ) {
            $tplargs->{error} = 'empty_cart';
        } else {
            my $shrate = calc_shipping_rate($user, $cart);
            if ( !defined $shrate ) {
                $tplargs->{error} = 'cant_ship_there';
            } else {
                my $res = finalize_order($user, $cart, $shrate);
                if ( $res->{status} eq 'ko' ) {
                    $tplargs->{error} = 'processing_error';
                } else {
                    $tplargs->{success} = 1;
                }
            }
        }
    }
    render_template('checkout.html', $tplargs);
}

Not he best, uh?

The second attempt moves everything to a single level, using return statements. Without nesting, code is more straightforward but longer, especially the render_template('checkout.html', $tplargs); instruction.

sub checkout() {
    my $tplargs;

    if ( !is_user_logged() ) {
        $tplargs->{error} = 'not_logged';
        render_template('checkout.html', $tplargs);
        return;
    }

    my $user = get_user();
    my $cart = get_cart_contents();

    if ( !@$cart ) {
        $tplargs->{error} = 'empty_cart';
        render_template('checkout.html', $tplargs);
        return;
    }

    my $shrate = calc_shipping_rate($user, $cart);
    if ( !defined $shrate ) {
        $tplargs->{error} = 'cant_ship_there';
        render_template('checkout.html', $tplargs);
        return;
    }

    my $res = finalize_order($user, $cart, $shrate);
    if ( $res->{status} eq 'ko' ) {
        $tplargs->{error} = 'processing_error';
        render_template('checkout.html', $tplargs);
        return;
    }
    
    $tplargs->{success} = 1;
    render_template('checkout.html', $tplargs);
}

Enter defer, which allows to define at the top (or somewhere else) in the sub something which must happen at the end of the execution scope.

sub checkout() {
    my ($error, $tplargs);

    defer {
        $tplargs->{error} = $error;
        render_template('checkout.html', $tplargs);
    }

    if ( !is_user_logged() ) {
        $error = 'not_logged';
        return;
    }

    my $user = get_user();
    my $cart = get_cart_contents();

    if ( !@$cart ) {
        $error = 'empty_cart';
        return;
    }

    my $shrate = calc_shipping_rate($user, $cart);
    if ( !defined $shrate ) {
        $error = 'cant_ship_there';
        return;
    }

    my $res = finalize_order($user, $cart, $shrate);
    if ( $res->{status} eq 'ko' ) {
        $error = 'processing_error';
        return;
    }
    
    $tplargs->{success} = 1;
}

Cleaner and elegant, isn’t it? It is also informative, as reading the beginning of the sub, it’s immediately obvious what happens at the end.

I think defer will become a very appreciated addition to the Perl language.