Perl 06:读取缺失的嵌套键,也可能修改输入
解析器接到 {} 后,写 $tree->{missing}{count} 看似只是问一个数字。可是要访问第二层,Perl 会把第一层的 missing 自动创建为哈希引用。若随后把这棵树当作原始记录转交,单纯的“探测”已经修改了输入。这个例子只说明这一种解引用路径,不声称所有 exists、赋值和类型组合都会以同一方式自动创建。
运行 perl examples/perl-language/experiments/06-autovivify.pl,STDOUT 依次为 before=0、after-read=1、created=HASH,此时读取的 $value 仍未定义。脚本随后对另一份空树先检查 exists $safe->{missing},显式拒绝,STDERR 是 missing field,退出码 255。这段反例的失败不是编译器自动给出数据契约,而是读代码的人主动定义了缺键不可接受的规则。测试 t/05-08-boundaries.t 同时断言了意外创建和拒绝诊断。
在累计工具里,外部日志只在读入后被构造成四个明确字段:time、level、service 和 message。读写各阶段不应通过嵌套缺键探测来“发明”一个默认结构;要容忍缺键,应先决定默认值并产生独立记录。undef 也可能参与自动创建,因此 04 的 defined 判断不能机械替代整个对象的结构校验。
补充断言把自动创建和引用别名放在同一输出中:autoviv_keys=missing value=undef。读取 $tree->{missing}{count} 时,最终值仍是 undef,但第一层键已经出现。这个结果说明“读到 undef”不是“什么都没发生”。若后续代码只检查值未定义,却把结构交给报表或 JSON 输出,输出形状已经被一次探测改变。
安全路径不是禁止所有嵌套结构,而是先判断结构再解引用:exists $tree->{missing} 只检查第一层;若存在,再判断 ref $tree->{missing} eq 'HASH',最后读取 count。课程测试保留自动创建的反例,是为了让读者看到缺键访问本身可能有副作用,而不是把 exists 神化成完整 schema 校验。官方 perlref 在 Using References 中说明 autovivification;本工具的记录结构仍由四字段解析契约决定。
| 操作 | 值结果 | keys %$tree |
引用形状 | 课程结论 |
|---|---|---|---|---|
只建 {} |
无 | 空 | 无 missing |
初始输入未含该字段 |
exists $tree->{missing} |
假 | 空 | 无 missing |
只查第一层不创建中间哈希 |
$tree->{missing}{count} |
undef |
missing |
missing 变成 HASH 引用 |
查询动作已经改变结构 |
先建独立 %next_batch |
ERROR |
各自独立 | 另一个哈希引用 | 跨 batch 不共享恢复状态 |
同一类别名风险也出现在 grep 和 map。course-semantics.pl filter_forms 先用 map { +{ %$_, service => uc $_->{service} } } 返回副本,原始 source=api|worker 不变;随后故意在 grep 代码块里写 $_->{service} = uc $_->{service},输出 grep_alias_source=API|WORKER selected=API。grep 的 $_ 是当前元素别名;把筛选条件写成赋值,既做判断又改输入。
练习:my $tree = {}; my $x = $tree->{a}{b}; 后再看 keys %$tree,为什么不是零?对 a 后继续解引用要求中间层有合适的容器。若业务只允许已有键,应先核结构再访问,不能先读完再检查“原来没有改变”。实验记录与文档来源见 examples/perl-language/evidence/05-08.md、5.38.2 官方 pod/perlref.pod(Using References 中的 autovivification)。









