如何实现Perl Moose属性的完全不可变?
在Moose中实现内部不可变的引用属性
问题描述
Moose里的ro属性仅能保护引用本身不被重新赋值,却无法阻止修改引用的内部结构。比如下面的代码中,push操作可以直接修改list属性的数组内容:
package SomeClass; use Moose; has 'list' => ( is => 'ro', isa => 'ArrayRef', ); package main; use warnings; use strict; use v5.36; use feature qw(try); no warnings qw(experimental::try); use Data::Printer; my $x = SomeClass->new({ list => [qw(a b c)] }); # 希望禁止这个操作 push $x->list()->@*, 'd'; p $x; # 输出结果:list被修改 #list [ # [0] "a", # [1] "b", # [2] "c", # [3] "d" # ]
我们期望实现类似Const::Fast的效果——修改数组内部时抛出错误:
use Const::Fast; const my $LIST => [qw(x y z)]; try { push $LIST->@*, 'no way'; } catch ($e) { warn $e; } # 报错:Modification of a read-only value attempted at ... line ... # 输出的LIST保持只读状态
解决方案
方法1:通过coerce生成只读引用
使用coerce特性,将传入的数组引用复制一份并转为只读。这种方式既保护了类属性的内部结构,也不会影响外部原数组:
package SomeClass; use Moose; use Const::Fast; has 'list' => ( is => 'ro', isa => 'ArrayRef', coerce => 1, builder => sub { [] }, # 可选,提供默认空数组 coerce => sub { my $original_arr = shift; # 复制原数组,生成新的只读引用 const my $readonly_arr => [@$original_arr]; return $readonly_arr; }, ); package main; use warnings; use strict; use v5.36; use feature qw(try); no warnings qw(experimental::try); use Data::Printer; my $x = SomeClass->new({ list => [qw(a b c)] }); try { push $x->list()->@*, 'd'; } catch ($e) { warn $e; # 抛出预期错误 } p $x; # list保持初始的[a,b,c],且每个元素和数组本身都是只读的
方法2:通过trigger标记传入的引用为只读
如果希望直接将传入的引用转为只读(注意:这会修改原引用的可写性,外部原数组也会变成只读),可以使用trigger:
package SomeClass; use Moose; use Const::Fast; has 'list' => ( is => 'ro', isa => 'ArrayRef', trigger => sub { my ($self, $arr) = @_; const $arr; # 直接标记传入的数组引用为只读 }, );
效果验证
运行修改后的代码,尝试修改list内部元素或数组结构时,会抛出Modification of a read-only value attempted的错误,数组内容保持不变,和Const::Fast的行为一致。
内容的提问来源于stack exchange,提问作者Jan
相关产品推荐
相关产品推荐

