星期六, 5月 01, 2010

perl 的 'exists' in array, hash references

今天在寫 perl 發現奇怪的 bug,Glib::Timeout 收到 unhandled exception 就停掉了,看了 error log,這我明明就處裡的好好的阿!
簡化過的問題如下:
#!/usr/bin/perl
use strict;
                                                                                                                                                              
sub retundef {
    #something wrong
    return undef;
}  

my $ret = &retundef();
if(exists $ret->{dummy}) {
    print "gasp!?\n";
}  
exit 1 unless $ret;
if(@{$ret->{list}}) {
    local $, = ", ";
    print @{$ret->{list}};

}
exit 0;
看起來很簡單的 code 卻暗藏玄機,居然 return 0; why??? 我左看右看,才想到可能是 exists 的問題。perldoc 的 exists 是這樣寫的:

Although the mostly deeply nested array or hash will not spring into existence just because its existence was tested, any intervening ones will. .... This happens anywhere the arrow operator is used...
原來在 $ret->{dummy} 以後, $ret 就自動變成 hash reference 了,雖然 $ret->{dummy} 還是不存在,但是卻改變了 $ret。
------------
不得不說,喜歡 C 不是沒有原因的... perl 寫起來方便(光是 hash table 就大勝) 但是對於一些 reference,包括 GC 的處理上的確不太透明;看來我要學得東西還很多...

4 則留言:

c9s 提到...

應用 defined()

Freehaha 提到...

defined 應該也一樣吧, 主要是在 ->{dummy} 的時候, $ret 變成 hashref 了

Freehaha 提到...

給你參考這個 https://gist.github.com/662096

c9s 提到...

咦對~

不過因為 defined 和 exists 基本上就是要對 hashref 操作的,所以也才會自動轉型。

但是 其實應該很少人這樣寫

如果要對 var 要做 exists 或 defined ,那就要假定 var 是 hashref 囉 ^^

所以在 exit 1 unless $ret; 的部份

通常會寫成 exit 1 unless %$ret;

然後在傳 $ret 之前建議一開始就指定 $ret ||= {}; 。