There's no interpolation involved. The sub contains a lookup of a variable's content, so the variable needs to remain unchange if you want the output of the lookup to be constant.
You can do this by copying the value of $str into into a variable that doesn't change, and using that variable instead.
sub make_closure {
my ($str) = @_;
return sub { $str };
}
my $str = 'hi';
my $sub = make_closure($str);
say $sub->(); # 'hi'
$str = 'hello';
say $sub->(); # 'hi'
You could actually interpolate the current value into code, but producing code is trickier.
my $str = 'hi';
my $sub = eval qq{ sub { "\Q$str\E" } } or die $@;
say $sub->(); # 'hi'
$str = 'hello';
say $sub->(); # 'hi'