10

Scalar::Util::weaken says:

NOTE: Copying a weak reference creates a normal, strong, reference.

I can't understand why Perl handle it this way. In my applications, I use weaken to break cycles. Sometimes i have to weaken references that would be already weak if Perl wouldn't act this way.

Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
key_
  • 577
  • 4
  • 15

3 Answers3

13

Any time you copy a reference into a new variable, the reference count is incremented. This is true when copying a weak or strong reference.

my $obj = {};    # 1 reference to {} stored in $obj

my $copy = $obj; # 2 references

weaken $obj;     # 1 reference

at this point, if $copy goes out of scope, the reference count will fall to zero, and the memory will be freed. Now assume the following code:

my $newref = $obj;  # 2 references

undef $copy;        # 1 reference

If Perl preserved the weak reference in $newref, then the hash would be unexpectedly dealocated when $copy was cleared. This would break the expectation that when you copy a reference, it will at least stick around as long as the copy.

In short, if weak references persisted across an assignment, it would force you to litter your code with myriad weakness checks, and would require some other way to unweaken a variable, all to avoid the inevitable variable suicide issues.

Eric Strom
  • 39,821
  • 2
  • 80
  • 152
10

I think this is an encapsulation issue. If a third party library uses weak refs internally my code should not be expected to know ahead of time that when I make a copy of a reference that it might suddenly disappear on me. The usual expectation in Perl is that a ref will remain valid as long as it exists. When you call weaken you basically have promised that you will take the needed steps to check that the reference is still valid before you use it.

As a second reason the interface to weaken a strong copy of a weak ref is fairly strait forward.

my $new_ref = $old_ref; if (isweak($old_ref)) { weaken($new_ref); }

The code to do the same to get a strong ref if a weak ref created a weak ref is a bit more complicated.

my $new_ref;
if (ref($old_ref) eq 'ARRAY') {
    $new_ref = \@{$old_ref};
}
elsif (ref($old_ref) eq 'HASH') {
    $new_ref = \%{$old_ref};
}
elsif (.....

If you know the ref can only be one type you can save the if/elsif cascade and simply do the deref-reref, but it's still harder to judge why you dereferenced just to take a new reference. The next maintainer will try to 'fix' your code.

Ven'Tatsu
  • 3,565
  • 16
  • 18
3

I'm not sure why this is default behavior, but here is the solution based on code from the Scalar::Util documentation:

$ref  = \$foo;
$weak = isweak($ref);               # false
weaken($ref);
$weak = isweak($ref);               # true

# copying a weak reference creates a new strong one
$copy = $ref;
$weak = isweak($copy);              # false

# the solution is simply to weaken the copy
$weaken($copy);
$weak = isweak($copy);              # true

If you wanted to make a subroutine that took a weak reference as an argument and returned a weakened copy of that reference, that would be easy using the code shown above.

James Thompson
  • 46,512
  • 18
  • 65
  • 82
  • .. are you sure a subroutine can return a weak reference? It seems to me that any weakened reference created within a subroutine would be DESTROY-ed when that reference goes out of scope at the close of the subroutine – Dancrumb Dec 16 '10 at 18:58
  • Wouldn't returning a weak reference result in an implicit copy (thereby resulting in a strong reference being returned)? – Cameron Dec 16 '10 at 19:10
  • 2
    @Dancrumb, @Cameron, you can return an overloaded object that stores a weakened reference. – ysth Dec 17 '10 at 07:43