ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

rustc 错误码 E0073 深度解析:递归结构体为何“造不出第一个实例”,以及 Option 如何终止递归

rustc 错误码 E0073 深度解析:递归结构体为何“造不出第一个实例”,以及 Option 如何终止递归 rustc 错误码 E0073 深度解析递归结构体为何“造不出第一个实例”以及 Option 如何终止递归【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust导读本文围绕 rust 编译器rustc中一个已经停止使用的历史错误码E0073展开讲解它针对的递归结构体问题当定义一个结构体Foo其字段要求必须先拥有一个Foo实例才能构造新的Foo时将永远不存在“第一个实例”递归无法在值层面落地。文章会从该错误码的原始说明出发还原问题成因、修复手法并结合当前仓库中仍在使用中的相邻错误码 E0072 与编译器实现源码理清“递归类型错误”这一家族的前世今生。读完后你将能准确区分“类型无限大小”与“值无法构造”两类递归陷阱并掌握用Option、Box与枚举基例设计可实例化递归数据结构的实战套路。一、E0073 的错误码档案一段已停用的诊断E0073 的解释文档位于 compiler/rustc_error_codes/src/error_codes/E0073.md其第一行就给出明确状态声明Note: this error code is no longer emitted by the compiler.也就是说E0073 在当前 rustc 中已经不再被触发了该文档属于“保留编号 归档说明”性质的历史资料。与此同时0073这个编号仍然登记在 rustc 错误码总注册表中见 compiler/rustc_error_codes/src/lib.rs 中error_codes!宏展开列表的0073一项。该文件头部注释明确规定了维护纪律见 lib.rs 第 2124 行Donotremove entries from this list. Instead, just add a note to the corresponding markdown file saying that this error is not emitted by the compiler any more … and remove all code examples that do not build any more by marking them withignore (no longer emitted).这套约定保证了错误码文档永远可以按编号检索、又不会误导读者以为代码仍会触发该诊断。此外该注释还提到错误码文档需遵循 RFC 1567 的格式规范且宏内容会被 tidy 子系统的check_error_codes_docs检查见 lib.rs 第 1219 行。从源码检索情况看E0073这一字面量在 compiler 目录内仅存在于解释文档与注册表没有任何实际报错路径引用它——这与“no longer emitted”的标注相互印证。结论E0073 是 rustc 历史版本中的“递归类型构造性检查”如今已被新的递归类型诊断体系取代。二、原始问题还原永远造不出“第一个实例”E0073.md 对问题本质的描述非常凝练You cannot define a struct (or enum)Foothat requires an instance ofFooin order to make a newFoovalue. This is because there would be no way a first instance ofFoocould be made to initialize another instance!即若构造Foo的某个环节必须已经存在一个Foo那么整个过程没有起点。原始文档给出的报错示例struct Foo { x: BoxFoo } // error初看之下这个结构体似乎没有问题——BoxFoo是一个指针尺寸已知即 E0072 关注的“无限大小”并不成立。但问题出在值的生产链上想要构造一个Foo必须先准备字段x也就是一个BoxFoo一个合法的BoxFoo必须指向某个已存在的Foo实例于是又回到第 1 步——需要一个Foo才能制造Foo。这是一条在值层面无限循环、且不存在基例base case的生产依赖任何一次尝试初始化都会向“更内层”请求又一个Foo永无终点。类型系统在编译期无法凭空给你一个初始种子因此该类型的任何值都无法被构造出来属于“类型可表示、但集合为空”的构造性递归错误。用类型构造器推理的视角看Foo的可构造性方程是Foo BoxFoo方程在“值存在性”意义上无解必须在递归中出现一个不依赖Foo的分支才能“落地”。三、与 E0072 的本质区别无限大小 vs 无法构造要真正理解 E0073必须先分清它与现行错误码 E0072 是两个维度的问题维度E0072现行仍会触发E0073历史已停用关注对象类型的尺寸/布局是否收敛类型是否存在可构造的基例触发示例struct ListNode { head: u8, tail: OptionListNode }——tail直接内嵌ListNode无指针间接层struct Foo { x: BoxFoo }——已用指针但无法产生首个Foo失败原因求 size 时无限递归尺寸不收敛求值时依赖自己无初始实例解决思路引入间接层Box、、Rc打破尺寸递归引入可选/可空基例Option、枚举空变体终止值递归E0072 文档用尺寸方程直观展示了前者的问题见 E0072.md 第 2226 行size of ListNode 1 byte for head 1 byte for the discriminant of the Option size of ListNodetail: OptionListNode让结构体尺寸方程在右边再次出现自身编译期无法算出确定的字节数因此必须通过Box之类指针把递归藏到间接层之后。而 E0073 场景中x: BoxFoo已经把尺寸问题解决了却仍未给出“停止递归、直接制造一个Foo”的途径——两种错误互为表里共同构成 rustc 对递归 ADT 的完整防线。现行编译器在处理递归类型的诊断时把“递归但缺间接层”统一收敛到 E0072。在 compiler/rustc_query_impl/src/handle_cycle_error.rs 的报错路径中可以看到第 282296 行编译器会输出recursive type ... infinite sizeE0072对每个参与循环的字段贴上recursive without indirection标签并给出多片段建议把字段包裹为Box...提示文案为insert some indirection (e.g., a Box, Rc, or ) to break the cycle。这正是 E0073 所描述的“用指针破递归”思路在今日实现中的延续与泛化。四、标准修复用 Option 显式提供递归终点E0073.md 给出的修复方案是引入Option把“必须有值”改成“可以没有值”struct Foo { x: OptionBoxFoo }修复之后递归链立刻有了合法的起点——直接构造一个空链即可let first Foo { x: None };这里None就是整个递归的基例它不需要任何已有Foo参与于是“第一个实例”得以诞生后续每个Foo都可以挂在某个已有实例的Some(Box::new(...))上。可见Option的本质作用不是消除递归而是在递归类型中挖出一个“不引用自身”的终止分支。在此基础上可以自然延伸出两种同源套路用于真实的数据结构设计字段可空终止文档写法适合“链节点”语义每个节点记录Some(next)或None链尾。枚举式基例把递归结构写成枚举用独立变体充当叶子。例如把链表建模为enum List { Nil, // 基例不引用 List 自身 Cons { value: u8, next: BoxList }, // 递归分支通过 Box 间接引用 }其中Nil承担与None完全相同的“终止”职责同时让空与非空两种情况在类型层面显式可区分代码可读性与模式匹配体验往往更优。此外无论采用哪种写法递归分支都必须保持“引用在Box//Rc等间接层之后”这是保证类型尺寸可计算的前提对应现行 E0072 的要求而基例Option或空变体则保证类型在值层面“可落地”对应历史 E0073 的要求。两者齐备递归数据结构才既可布局又可构造。五、今日视角现代编译器如何处理这类代码由于 E0073 已停用现在直接书写struct Foo { x: BoxFoo }已不会再收到 E0073 报错。从仓库现状推断编译器的递归类型诊断已经演化为“以尺寸/布局检查为核心”的体系递归缺少指针间接层→ 尺寸方程不收敛 → 触发 E0072现行文档见 E0072.md并在 E0320.md 中作为“无限大小类型”被引用递归已有间接层但无基例→ 类型本身合法但任何实例化尝试都会要求你写出一个无限长的构造表达式最终由类型推导或递归深度限制兜底不再由专门的 E0073 负责。从实现证据看rustc_query_impl/src/handle_cycle_error.rs 中以 E0072 报错的循环检测逻辑recursive without indirection标签 Box…包装建议见第 258296 行已成为递归类型问题的主要诊断出口。E0073 的历史职责——提示“递归需要一个可构造的初始实例”——在实践中转化为编写者应当自觉遵守的设计约束即任何递归类型都必须带显式终止分支。六、给使用者的三条实操建议综合该文档与仓库现状在面对“结构体里想装一个同类型的自己”这类需求时可以按以下顺序自查先问递归字段直接内嵌了吗若字段类型在展开后会再次原样包含自身如struct Foo { x: Foo }或缺少Box的OptionList类型尺寸不可计算将触发现行 E0072。必须用BoxT、a T、RcT等间接层包裹让字段尺寸固定为指针大小。再问间接层之后递归链有基例吗若字段是BoxFoo且没有可终止分支对应历史 E0073 场景虽然能通过编译期的尺寸检查但该类型无法构造任何实例。应把字段改为OptionBoxFoo或改用带Nil这类空变体的枚举保证能写出Foo { x: None }这样的“首个实例”。最后问能用非递归方案替代吗若数据天然共享且成环如图、双向链表纯Box递归并不合适通常需要Rc/Weak或基于索引的平坦存储如Vecu8 下标。递归类型应只用于结构上确实呈“树/链”形态的数据。结语E0073 虽然已从 rustc 的诊断体系中退役但它的核心洞察——递归类型不仅要有确定尺寸还必须存在可构造的“第一个实例”——在今天依然是指引递归数据结构设计的关键原则。理解它与现行 E0072 的分工等于同时掌握了两条防线用Box//Rc打破尺寸递归用Option/枚举空变体打破值递归。二者组合才能写出既可通过编译器检查、又真正可实例化使用的递归类型。如需进一步查阅推荐顺序为先读本文所依据的历史文档 E0073.md再对照现行文档 E0072.md最后阅读错误码注册与维护约定 lib.rs 中关于“已停用错误码保留编号并加注说明”的规则即可完整理解 rustc 错误码体系“既保留编号、又如实标注状态”的文档治理方式。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表