ARTICLE DETAIL

资讯详情

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

Rustc 中的 Implied Bounds(隐含边界):编译器如何自动推导 `T: ‘a` 这类生命周期约束

Rustc 中的 Implied Bounds(隐含边界):编译器如何自动推导 `T: ‘a` 这类生命周期约束 Rustc 中的 Implied Bounds隐含边界编译器如何自动推导T: a这类生命周期约束【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本文以 Rust 编译器开发指南中的 Implied bounds 文档为主体结合当前仓库源码完整讲解 rustc 如何通过“显式explicit”与“隐式implicit”两条路径自动推导出代码中省略的生命周期约束如fn fooa, T(x: a T)隐含的T: a。读完后你将能够定位这些约束的推断入口inferred_outlives_crate、理解其不动点fixpoint算法与 WF 证明机制并解释文档中提及的几个已知 soundness 缺口产生的原因。背景为什么需要隐含边界在 Rust 中下面这个函数签名不需要也不允许显式写出T: afn fooa, T(x: a T) {}只要a T是良构well-formed即 WF的编译器就可以自由地假设T: a成立。这类“从签名类型中自动推导出来的约束”就是implied bounds隐含边界。它们的存在避免了强制用户在大量代码上添加显式注解。从实现角度看隐含边界分为两大类原文档的核心划分Explicit implied bounds显式隐含边界会被真正“物化”为谓词附加到相关项的fn clauses_of即该泛型项的约束子句集合中Implicit implied bounds隐式隐含边界不会写入clauses_of而是在需要它们的地方区域推理、MIR borrowck 等按需“隐式地”当作已知假设来使用并另行负责证明它们确实成立。这一区分在源码中也有直接对应clauses_of的实现在 clauses_of.rs 中注释说明其结果“包括explicit_clauses_of返回的所有子句再加上推断出的子句”即 explicit implied bounds。Explicit implied bounds为类型推断出显式约束哪些项拥有显式隐含边界原文档指出只有 ADTstruct/enum/union与 CTAchecked type alias拥有显式隐含边界。仓库中的术语表对此有解释CTA 指“被正确表示为别名AliasTy的类型别名”与会在 HIR lowering 时展开为底层类型的普通别名相对旧的 LTAlazy type alias已更名为 CTA术语表条目。入口函数inferred_outlives_of按DefKind分发Struct | Enum | Union从 crate 级查询inferred_outlives_crate的结果映射中取出该项的推断子句TyAlias且通过type_alias_is_checked检查即 CTA同样取自该映射AnonConst开启generic_const_exprs时若匿名常量来自泛型参数默认值则回退到父项例如struct Fooa, b, const N: usize { ... }(a b ())中匿名常量的ParamEnv需要继承Foo的子句见 源码中的注释示例其他项返回空。不动点算法inferred_outlives_crate查询inferred_outlives_crate查询先调用implicit_infer::infer_clauses计算推断子句再把结果转换为全局CrateClausesMap结构其中Ty::OutlivesClause被分别 upcast 为TypeOutlives或RegionOutlives子句泛型常量参数则被忽略“Generic consts dont impose any constraints”mod.rs#L84-L87。真正的推断逻辑在infer_clauses中它是一个不动点fixpoint算法loop { for 每个顶层项: 重新计算该项所需的全部 outlives 子句结合当前 global_inferred_outlives 若比上一轮多出子句 记录为 clauses_added if clauses_added 为空: 达到不动点, break if 轮次超出 recursion_limit: 报 fatal error }为什么需要多轮迭代源码中check_inferred_clauses的注释给出了典型例子struct Outera, T { outer: Innera, T } struct Innerb, U { inner: b U }第一轮处理Outer的字段时Inner的推断子句还是空的第二轮才会拿到Inner的U: b经实例化[b a, U T]后得到Outer需要T: a。因此“只要新加了子句就可能导致其父类型产生新的隐含子句”必须整体重算直到不动点implicit_infer.rs#L73-L93。该循环还带有一个保护当迭代轮次超出recursion_limit时编译器会直接报致命错误overflow computing implied lifetime bounds for …implicit_infer.rs#L94-L106——这是遇到循环嵌套生命周期结构时可能看到的真实诊断。仓库同时提供 dump.rs 模块用于把推断结果以 dump 形式输出方便调试。逐字段分解insert_required_clauses_to_be_wf原文档强调对 crate 中所有 ADT 的所有字段都会调用insert_required_clauses_to_be_wf“We usefn insert_required_clauses_to_be_wfon all fields of all ADTs in the crate”该函数对每个字段类型独立计算 WF 所需的子句。它遍历类型树ty.walk()按叶子类型分派叶子类型处理ty::Ref(region, rty, _)a T要求T: a调用insert_outlives_clause(tcx, rty, a, …)implicit_infer.rs#L130-L137ty::Adt同时检查该 ADT 的推断子句check_inferred_clauses与显式子句check_explicit_clausesimplicit_infer.rs#L139-L157ty::Alias(_, Free {..})CTA与 ADT 相同检查推断 显式子句implicit_infer.rs#L159-L178ty::Dynamicdyn Trait仅检查 trait 的显式子句且过滤引用Self的子句避免把 dummy Self 泄漏到子句映射中implicit_infer.rs#L180-L207ty::Alias(_, Projection {..})T as Trait::Assoc只检查父 trait 的显式子句不检查关联类型自身的implicit_infer.rs#L209-L222ty::Alias(_, Inherent {..})目前不做处理FIXME(inherent_associated_types)注释这与原文档的表述一一对应“For ADTs, trait objects, and associated types the initially required clauses are computed in [fn check_explicit_clauses]. This simply usesfn explicit_clauses_ofwithout elaborating them.” —— 即check_explicit_clauses直接取explicit_clauses_of的结果、按实参实例化后逐条交给insert_outlives_clause处理不做 elaboration。组件分解与“不添加static要求”最后一步是insert_outlives_clause它接收一个 outlives 约束T: a或b: a把左侧分解成 outlives 组件push_outlives_components只有当右侧被 outlive 的区域是区域参数时才把组件作为显式子句加入if !is_free_region(outlived_region) { return; }各组件的处理在源码注释中有完整示例utils.rs#L38-L126Component::Region来自a b u32这类字段产生b: aComponent::Param来自a VecU产生U: aComponent::Alias来自a T as Iterator::Item产生T as Iterator::Item: aComponent::EscapingAlias投影含 late-bound 区域如forb fn(b T as Iterator::Item)其 WF 要求在函数调用点检查而非类型定义处因此直接忽略Component::Placeholder/UnresolvedInferenceVariable分别为span_bug!/bug!不应出现在该路径。为什么不加static要求is_free_region中ReStatic被显式筛掉ty::ReStatic falseutils.rs#L158-L164。注释解释了原因field: static T会生成T: static这类“相当令人意外rather surprising”的约束因此不传播到头header上。同样被排除的还有ReBoundHRTB 内的区域无法在头处命名。Implicit implied bounds把约束当作假设使用为什么不能直接写成显式谓词原文档给出的理由是“我们还无法处理 binder 中的蕴含implications in binders”。函数和 impl 的签名类型含 late-bound 区域所要求的 outlives 约束不能简单作为显式谓词塞进ParamEnv——因此它们只能“隐式”地在需要处被当作假设。假设在哪些位置被注入关键点是这些边界不会加进受影响项自身的ParamEnv而是按消费方分别注入词法区域解析lexical region resolution通过OutlivesEnvironment::from_normalized_bounds加入。MIR borrowck通过UniversalRegionRelationsBuilder::add_implied_bounds加入。在 MIR borrowck 中隐含边界来源于函数签名和 impl 头free_region_relations.rs#L240-L316。其他位置对assumed_wf_types查询返回的类型加入其 outlives 要求。这里有一个容易忽视的细节原文档明确提出MIR borrowck 会对归一化normalized和未归一化unnormalized两种类型都添加 outlives 约束而词法区域解析只使用未归一化的类型。源码印证了这一点borrowck 侧先add_implied_bounds(ty)再fully_perform(Normalize …)后对norm_ty重复一次并注释说明“归一化可能引入新的区域变量#136547若不为其补加隐含边界归一化后的签名会因区域约束不满足而变为 not-WF”free_region_relations.rs#L258-L284并给出trait Foo { type Bar; fn foo(self) - Self::Bar; }的具体例子。impl 头部分还会把父项如 trait 关联函数所属的 impl的assumed_wf_types一并纳入free_region_relations.rs#L289-L316。从源码结构看该文件同时保留了旧求解器的create_old路径并带有FIXME(#160491)标记说明新旧 implied bounds 实现正处于并行过渡期与文档中“待 #160343 落地后更新函数链接”的 FIXME 相互印证。assumed_wf_types哪些类型被“视为良构”assumed_wf_types查询按项的种类返回“被假定为 WF 的类型”列表是隐式隐含边界的类型来源Fn/AssocFn函数签名的输入与输出先做liberate_late_bound_regions释放 late-bound 区域关联函数还会叠加父项的类型Impltrait impl 取 trait 参数 Self 类型固有 impl 取 Self 类型AssocTyRPITITimpl Traitin trait把关联函数的 assumed WF 类型中的ReLateParam重映射到 opaque 的 early-bound 生命周期或从 trait 侧 RPITIT 实例化继承implied_bounds.rs#L58-L126AssocConst/AssocTy继承父项其余结构项ADT、常量等返回空列表。implied_outlives_bounds查询从 WF 义务中提取区域关系原文档称隐式边界的假设约束由implied_outlives_bounds查询计算“它直接从fn wf::obligations中提取所需的 outlives 边界”。在当前仓库中该查询的 provider 位于 rustc_traits/src/implied_outlives_bounds.rs实际计算在compute_implied_outlives_bounds_inner维护一个wf_args栈初始同时放入未归一化与归一化两种类型因为“WF 论证的谓词不保证是原类型的子集”rustc_trait_selection#L52-L58对每个类型调用wf::unnormalized_obligations拿到全部 WF 义务深度归一化后只保留区域关系RegionOutlives直接转为OutlivesBound::RegionSubRegionTypeOutlives则再经push_outlives_components分解为RegionSubParam/RegionSubAlias等约束implied_bounds_from_components遇到的WellFormed谓词会被压栈继续展开。此外查询入口还有一个性能快路径空元组、!、str、基本数值类型直接返回空约束rustc_trait_selection#L22-L34以及一个源码中明确标注的兼容性 hackconsider_implied_bounds_hack_for_ty会在检测到参数包含bevy_ecs::ParamSet时把“注册的区域义务”当作隐含边界使用且可被-Zno-implied-bounds-compat关闭rustc_trait_selection#L125-L149关联 issue #119956。这段代码印证了隐含边界机制对下游生态的影响之大足以保留一个专门的兼容开关。证明隐式隐含边界WF 谓词及其局限由于隐式隐含边界不在clauses_of中编译器必须单独确保它们确实成立。文档给出的策略是发出WellFormed谓词检查所有被使用的类型是良构的即“用的类型都 WF则其中的隐含 outlives 约束必然被 WF 论证覆盖”。但这一策略有两个明确的边界文档逐一列出源码亦可对应实例化 impl 时不能发出 WF 谓词否则会造成 trait solver 循环文档称“currently often inductive”即当前往往构成归纳式循环不发出涉及 higher-ranked regionHRTB的约束因为缺少来自其 binder 的隐含边界无法正确论证。文档随后指出上述取舍导致了多个已知 soundness 缺口原文以 issue 编号给出此处同样以编号引用经由**子类型关系subtyping**引入issue #25860经由对 higher-ranked trait bound 的supertrait upcasting引入issue #84591使用 impl 时能归一化某个投影、检查该 impl 时却不能归一化issue #100051。这些缺口说明隐含边界目前是一个“实用优先”的机制它默认了调用方已经证明的约束而证明路径存在被绕过的缝隙。这也是文档把“proving implicit implied bounds”单独成节的原因——理解这条证明链及其例外是理解 rustc 生命周期检查现状的关键。实践要点如何观察与定位隐含边界基于上文源码路径阅读/调试 rustc 隐含边界时可以从以下入口入手均为当前仓库可查证的位置想看某个 ADT/CTA 最终获得哪些显式隐含边界从 outlives/mod.rs 的inferred_outlives_of/inferred_outlives_crate入手推理过程在 outlives/implicit_infer.rs想理解一个a X字段如何变成…: a子句读 outlives/utils.rs 的insert_outlives_clause与is_free_region想理解某处“为什么编译器假设了T: a”分别检查 rustc_ty_utils/src/implied_bounds.rs哪些类型被假定 WF与 rustc_trait_selection/src/traits/implied_outlives_bounds.rs从 WF 义务提取区域约束想看 MIR borrowck 如何把这些假设变成区域约束读 rustc_borrowck/src/type_check/free_region_relations.rs遇到overflow computing implied lifetime bounds诊断对应 implicit_infer.rs#L94-L106 的不动点超限保护通常意味着生命周期约束沿嵌套类型循环传播。小结rustc 的 implied bounds 机制由两套互补路径构成显式路径只服务于 ADT 与 CTA通过inferred_outlives_crate的不动点迭代把字段类型分解出的T: a/a: b子句固化进clauses_of并刻意排除static与 HRTB 区域隐式路径则绕过 binder 蕴含的限制在词法区域解析与 MIR borrowck 中按需把签名/impl 头的 WF 类型当作假设经assumed_wf_types与implied_outlives_bounds两个查询提取区域关系并靠发出WellFormed谓词来反向保证假设成立。理解这两条路径的分工及其各自的例外impl 实例化不发 WF、HRTB 约束缺失、bevy_ecs::ParamSet兼容 hack才能准确解释 rustc 在省略生命周期注解时的行为与文档列出的已知 soundness 缺口。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表