资讯动态

Rust 编译器中的 Unsize 与 CoerceUnsized:rustc 如何实现动态大小类型的自动转换

发布时间:2026/9/13 8:50:07 来源:尧图企业网站定制
Rust 编译器中的 Unsize 与 CoerceUnsizedrustc 如何实现动态大小类型的自动转换【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust本篇技术指南基于 rustc 开发指南rustc-dev-guide中关于 unsizing 机制的专章深入剖析编译器内部对Unsize与CoerceUnsized两个核心 trait 的处理逻辑二者如何分工、内置的原始实现与结构化实现、以及dyn Traitupcasting 的三步算法。读完本文你将掌握 rustc 是如何判断一个类型允许被 unsize以及智能指针容器如何转发 unsize 行为的底层原理并能对照源码定位相关实现与测试。一、两个 trait 的分工容器与内容Rust 标准库中有两个看起来相似、实则职责不同的 traitrustc 开发指南开篇就划清了界限CoerceUnsized关注数据容器。当一个结构体典型如智能指针实现了CoerceUnsized意味着它内部指向的数据正在被 unsize。典型实现者包括TArcTBoxT该 trait 的设计目标eventually是允许用户自定义的智能指针实现它从而让RcT、ArcT、BoxT这类容器能够从T自动转换到dyn Trait或[T]。实现该 trait 的合法性规则在其标准库文档中有详细说明例如要求被指向的字段必须是结构的最后一个字段、且该字段类型允许被 unsize 等。Unsize则关注实际被 unsize 的类型本身。它回答的问题只有一个某个具体类型是否允许被 unsize 成另一个类型。Unsize从不打算被用户实现这背后有一个深刻原因Unsize并不告诉编译器尤其是 codegen如何去 unsize 一个类型而只告诉它是否允许unsize。实际的布局转换如何从[T; N]的胖指针变成[T]的切片指针、如何生成 vtable 指针等完全由 codegen 阶段理解类型的表示方式后自行完成。因此该 trait 与 codegen 的实现紧密配对paired somewhat intimately这也是它不能开放给用户手工实现的原因——一旦放开用户既无法提供正确的转换指令又可能给出与 codegen 实际表示不一致的错误承诺。二、内置的原始 unsizing 实现Unsize为以下两种基础情形提供了内置实现built-in implementations不依赖任何用户代码T-dyn Trait a当T: Trait且T: Sized a且Trait是 dyn-compatible 的即旧称 object safe时成立。这是具体类型擦除为 trait 对象的底层支撑BoxT到Boxdyn Trait的转换最终都归结到这条规则。[T; N]-[T]定长数组到切片Box[T; N]到Box[T]的转换即依赖此条。此处要求源与目标元素类型完全一致即只允许去掉长度信息。在源码中候选生成逻辑位于 candidate_assembly.rs 的assemble_candidates_for_unsizing函数第 986 行起它按(source, target)类型对的形状分派(_, dyn)即T-dyn Trait直接压入BuiltinUnsizeCandidate([T; N], [T])即数组转切片同样压入BuiltinUnsizeCandidate两端的推断变量Infer(..)则标记为ambiguous等待后续求解。而具体的确认逻辑如何构造嵌套约束在 confirmation.rs 的confirm_builtin_unsize_candidate中第 1033 行起T-dyn Trait逐条检查 trait 对象上的每个约束principal、auto trait、projection bound生成将T转换为Trait的义务并要求T: Sized只能从 Sized 类型构造对象同时要求T存活于a注册T: a的 outlives 约束。[T; N]-[T]通过eq统一元素类型保证N之前的元素类型一致。另外需要注意候选组装时对高阶约束higher-ranked obligations例如fora a T: UnsizeTrait a是保守拒绝的。源码注释解释了原因Unsize义务总是作为CoerceUnsized实现的一部分出现而这些实现通常作用于具体类型虽然理论上可以扩展支持但现有场景没有强需求且这类 where 子句往往可以直接写成T: Trait。三、结构化实现struct 尾部字段 unsizing除了上述原始情形Unsize还有一条可视为结构化structural的实现Struct.., Pi, .., Pj, ..: UnsizeStruct.., Ui, .., Uj, ..当且仅当尾部字段满足TailFieldPi, .., Pj: UnsizeUi, .., Uj。其中要求被 unsize 的尾部字段是结构体中唯一涉及泛型参数Pi, .., Pj的字段这些参数不必连续。这条规则之所以略微复杂是因为它允许不止一个泛型参数发生变化且这些参数不必然被 unsize例如宽度参数可能保持不变而仅让尾部字段对应的参数变化因此文档建议以结构的尾部字段为视角来理解整个结构的 unsize 最终被约简为尾部字段自身的 Unsize 义务。源码中的实现印证了这一设计。在 confirmation.rs 中通过tcx.unsizing_params_for_adt(def.did())取得该 ADT 允许 unsize 的参数下标集合若为空则直接判定Unimplemented取def.non_enum_variant().tail()得到尾部字段并将其类型分别在源参数args_a与目标参数args_b下实例化期间做归一化normalize_with_depth_to得到source_tail与target_tail构造一个源结构体但参数替换为目标 unsize 参数的新类型new_struct用eq验证它确实等于目标类型确保只有尾部字段相关的参数被改变最后构造嵌套义务TailFieldT: UnsizeTailFieldU并压入nested交由递归求解。候选组装阶段candidate_assembly.rs 第 1100-1105 行只要求源与目标都是同一个struct 的 ADT 类型def_id_a def_id_b即压入BuiltinUnsizeCandidate具体的参数匹配与尾部字段检查全部留待确认阶段完成。元组 unsizing 的历史文档还记录了元组 unsizing 的过往它曾位于unsized_tuple_coercionfeature gate 之后但其实现已在 PR #137728 中被移除。因此现阶段元组不能参与结构化 unsizing这一点与尾部字段规则中的 struct-only 约束是一致的。四、Upcasting 实现dyn trait 之间的转换编译器内部把两种操作都称为 upcasting它们看似不同却在类型系统中由几乎相同的代码处理真正的 upcastingdyn SubTrait-dyn SuperTrait。这会调整 dyn trait 的 vtable指针元数据在运行时需要改变。去掉 auto trait 并调整生命周期dyn Trait AutoTraits... a-dyn Trait NewAutoTraits... b条件是AutoTraits包含NewAutoTraits且a: b。这类转换是运行时 no-op不需要改变指针元数据。两种操作在候选组装阶段被统一处理当源与目标是两个 dyn 类型且 principal 相同、或目标没有 principal 时检查源的所有 auto trait 是否覆盖目标的 auto traitcandidate_assembly.rs 第 1030-1051 行注意源的 auto trait 集合还包括 principal 的 supertrait 中隐含的 auto trait通过后压入BuiltinUnsizeCandidate而确认阶段confirmation.rs 第 1048-1102 行会保留源的 principal 与 projection、采用目标的 auto trait 列表重建新类型并额外注册a: b的 outlives 义务——正对应文档所述只改变生命周期边界。而真正更换 principal的 upcasting 则进入TraitUpcastingUnsizeCandidate分支候选组装阶段枚举源 principal 的全部 supertraitutil::supertraits逐一探测是否与目标 principal 的 def-id 一致一致则调用match_upcast_principal做进一步核对并压入候选确认阶段confirm_trait_upcasting_unsize_candidate第 995-1031 行断言源与目标必须都是dyn类型取出源 principal 的第idx个 supertrait 作为 upcast 目标再通过match_upcast_principal完成最终的约束检查。三步 upcasting 算法这套内置实现是Unsize中最复杂的部分尤其在 PR #114036 为支持关联类型的复杂性而重做之后。文档将其算法概括为三步——对源 dyn trait principal 的每一个supertrait含自身执行统一 super trait ref 与目标的 principal确保我们只会 upcast 到真正的 supertrait而绝不经由某个 impl达成存在专门的反例测试 illegal-upcast-from-impl.rs 防止这种非法 upcast。逐一核对目标的每个 auto trait 是否存在于源中允许丢弃auto trait但绝不允许凭空新增。逐一核对目标的每个 projection关联类型投影能否与源中的某个单一 projection 统一因为可能存在trait Sub: Sup.., A i32 Sup.., A u32这样的多个投影此时必须要求统一到单一一个从而避免让投影约束不必要地引导类型推断不过在无歧义时投影仍可以引导推断。第 3 步正是关联类型复杂性的来源若不约束单一投影推断器会在多个候选投影之间无所适从而若场景无歧义又允许推断继续推进这是一种兼顾确定性与灵活性的设计。文档同时以脚注明确了术语principal指dyn Trait中唯一的一个非 auto trait。五、在 rustc 中的完整脉络从候选到确认综合来看rustc 处理Unsize/CoerceUnsized义务的路径是标准的候选组装 → 确认两段式组装candidate_assembly.rs按(source, target)形状分派——dyn 到 dyn含 upcast、具体类型到 dyn、数组到切片、struct 到同 struct、推断变量标记 ambiguous其余形状不产生候选确认confirmation.rs把候选展开为具体的嵌套义务——dyn 到 dyn 检查 auto trait 兼容与生命周期T到 dyn 检查 dyn-compatible 与Sized、outlives数组到切片统一元素类型struct 到 struct 提取尾部字段并递归Unsize义务。其中T: UnsizeU义务通常总是作为T: CoerceUnsizedU实现的一部分被使用这也解释了为什么针对高阶形式带绑定生命周期的Unsize义务会被保守地拒绝——因为这类义务在实践中几乎总是作用于具体类型。若读者想进一步深入本主题推荐继续阅读 traits 目录 下的相关章节如 trait 解析与选择机制并对照上述两个源码文件以及 trait-upcasting 测试目录 中的正反用例理解完整行为。通过本文的源码级对照你可以看到Unsize是一张允许清单CoerceUnsized是容器的转发声明而真正完成指针元数据调整的始终是与二者紧密配对的 codegen 层。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价