Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nested closures and captured variables

I have this example with nested closures which demonstrates memory leak

use v5.10;
use strict;

package Awesome;

sub new {
    bless {steps => [], surprise => undef}, shift;
}

sub say {
    print "awesome: ", $_[1], "\n";
}

sub prepare {
    my ($self, @steps) = @_;

    for my $s (@steps) {
        push @{$self->{steps}}, sub {
            $self->say($s);

            if ($s eq 'pony') {
                $self->{surprise} = sub {
                    $s;
                }
            }
        };
    }
}

sub make {
    my $self = shift;

    while (my $step = shift @{$self->{steps}}) {
        $step->();
    }

    if ($self->{surprise}) {
        printf("And you have surprise: %s\n", $self->{surprise}->());
    }
}

sub DESTROY {
    warn "destroying";
}

package main;

my $a = Awesome->new;
$a->prepare('barbie','pony','flash');
$a->make();

Output on my perl is

awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
destroying at /tmp/t.pl line 43 during global destruction.

And this "during global destruction" means that object couldn't be destroyed in normal way, because it has some circular references.

However the only circular references are created by

push @{$self->{steps}}, sub {
            $self->say($s);

where we are using $self inside first closure. And later inside make() we'll delete this steps and circular references. But looks like this nested closure with "suprise" makes problems. For example if we'll not pass "pony" to prepare() output will be good as expected:

awesome: barbie
awesome: flash
destroying at /tmp/t.pl line 43.

So, is nested closures in perl captures the same variables as closure from upper level already captured even if we are not using them?

like image 408
Oleg G Avatar asked Feb 10 '23 16:02

Oleg G


1 Answers

Perl used to over-capture in nested closures before, but it does not do so since 5.18.

$ tail -n 9 a.pl   # Modified to make clearer when the object is destroyed.
package main;

{
   my $a = Awesome->new;
   $a->prepare('barbie','pony','flash');
   $a->make();
}

print "done.\n";

$ 5.16.3t/bin/perl a.pl
awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
done.
destroying at a.pl line 43 during global destruction.

$ 5.18.2t/bin/perl a.pl
awesome: barbie
awesome: pony
awesome: flash
And you have surprise: pony
destroying at a.pl line 43.
done.
like image 156
ikegami Avatar answered Feb 16 '23 01:02

ikegami