拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Carbon 语言泛型设计解析:允许与 `final impl` 重叠的一致性 `impl`(提案 P2868)

Carbon 语言泛型设计解析允许与final impl重叠的一致性impl提案 P2868【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang导读本文基于 Carbon Language 仓库中的设计提案 proposals/p002868-allow-overlap-with-a-final-impl-if-identical.md深入解析 Carbon 泛型系统中一条精细但关键的规则当一个普通impl与某个final impl重叠时只要它们在重叠区域一致agree就允许共存。该规则直接服务于CommonTypeWith这类只含关联常量、不含函数成员的接口在容器类型如Vec上的递归实现本文将从设计动机、规则定义、编译器落地toolchain/check源码与测试到真实用例逐层展开帮助读者理解 Carbon 如何在不引入复杂特化机制的前提下保持泛型系统的连贯性。问题背景CommonTypeWith的final impl挡住了什么条件表达式与公共类型Carbon 的if条件表达式需要计算两个分支的公共类型common type这一机制由接口CommonTypeWith承载定义见 docs/design/expressions/if.md 中的设计说明interface CommonTypeWith(U: type) { let Result: type; }A as CommonTypeWith(B)的实现指明当A与B出现在同一条件表达式中时二者共同推导出的结果类型。围绕它还衍生出CommonType约束与对称版本SymmetricCommonTypeWith以及一组默认的 blanketimplinterface SymmetricCommonTypeWith(U: type) { let Result: type; } impl forall [T: type, U: CommonTypeWith(T)] T as SymmetricCommonTypeWith(U) where .Result U.Result {} impl forall [U: type, T: CommonTypeWith(U)] T as SymmetricCommonTypeWith(U) where .Result T.Result {}兜底的final impl类型与自身求公共类型任何类型T与它自身求公共类型结果自然是T本身。这是所有类型都成立的性质因此当前设计docs/design/expressions/if.md为它声明了一个 blanket 形式的final implfinal impl forall [T:! type] T as CommonTypeWith(T) where .Result T {}关键问题在于根据 docs/design/generics/details.md 中final impl declarations一节的规则标记为final的impl会禁止任何在重叠规则overlap rule下比它更具体的重叠实现——即使这种重叠在语义上是完全无害的。例如下面这段我们希望合法定义的实现impl forall [U:! type, T:! CommonTypeWith(U)] Vec(T) as CommonTypeWith(Vec(U)) where .Result Vec(T.Result) {}Vec(T)与T在类型结构上存在重叠当T Vec(U)时按旧规则它会被final impl forall [T:! type] T as CommonTypeWith(T)拦截编译器直接报错。但这两个实现实际上并不冲突CommonTypeWith接口只有Result一个成员且是关联类型常量不含任何函数成员在两个实现重叠的区域里Result的取值完全一致——T.Result与Vec(T).Result Vec(T.Result)语义自洽。提案 P2868 要解决的就是这一矛盾允许与final impl重叠、但在重叠区域语义一致的impl存在。这是 Carbon 团队在 question-for-leads issue #1077find a way to permit impls of CommonTypeWith where the LHS and RHS type overlap中收集多种方案后敲定的结果。什么是final impl为什么需要不可特化承诺在进入提案细节前先回顾final impl的动机它定义于 docs/design/generics/details.md 的 finalimpl declarations 一节约 L5153 起。当编译器知道某个参数化impl**永远不会被更具体的实现特化specialize**时它就能安全地使用该impl中关联常量的赋值来推导泛型代码中的类型。设计文档用前缀解引用运算符Deref举例// 定义前缀 * 运算符行为的接口 interface Deref { let Result: type; fn Op(self) - Result; } // 实现 Deref 的类型 class Ptr(T: type) { ... impl as Deref where .Result T { fn Op(self) - Result { ... } } } class Optional(T: type) { ... impl as Deref where .Result T { fn Op(self) - Result { ... } } } fn FT: type { // 在实现中使用 Ptr(T) 和 Optional(T) }如果允许Optional(T) as Deref或Ptr(T) as Deref在某个更具体的T上被特化编译器就无法对Deref.Op调用的返回类型做任何假设F就不得不写出冗长且暴露实现细节的约束fn FT: type where Optional(T).(Deref.Result) .Self and Ptr(T).(Deref.Result) .Self { // 在实现中使用 Ptr(T) 和 Optional(T) }解决办法就是给impl加上final修饰符声明不可特化class Ptr(T: type) { ... final impl as Deref where .Result T { ... } } class Optional(T: type) { ... final impl as Deref where .Result T { ... } } // ❌ 非法impl Ptr(i32) as Deref { ... } // ❌ 非法impl Optional(i32) as Deref { ... }一旦编译器看到匹配的final impl就可以直接使用其中的关联常量赋值fn FT: type { var p: Ptr(T) ...; // *p 的类型就是 T var o: Optional(T) ...; // *o 的类型就是 T }提案核心允许重叠但必须一致一致agree的定义提案给出的规则非常克制允许一个impl声明与final impl声明重叠当且仅当二者在重叠区域一致。一致的精确定义是所有值value比较相等——主要是关联常量尤其是关联类型associated type如CommonTypeWith.Result函数永不比较相等——即编译器不承担比较两个函数定义是否相同的义务。由于我们不要求编译器去比较函数定义一致只有在接口不包含任何函数成员时才有可能达成。这也是为什么该规则能解决CommonTypeWith纯关联常量接口的问题却不会打开函数级一致性比较这个复杂得多的潘多拉魔盒。重叠如何计算类型结构的合一规则的实现细节已并入 docs/design/generics/details.md 的 finalimpl declarations 一节。两个非template的impl声明之间是否重叠通过**对相应部分做类型合一unification**来计算。以设计文档中的例子说明final impl forall [T: type] T as CommonTypeWith(T) where .Result T {} impl forall [V: type, U: CommonTypeWith(V)] Vec(U) as CommonTypeWith(Vec(V)) where .Result Vec(U.Result) {}将T与Vec(U)合一、将CommonTypeWith(T)与CommonTypeWith(Vec(V))合一得到交集条件是T Vec(U)且U V。在这个交集上两个实现的.Result分别是T即Vec(U)与Vec(U.Result)即Vec(V)取值一致因此第二个impl合法。对于template的impl声明重叠与一致性检查会延迟到模板以具体类型实例化时再进行——因为模板实例化前无法确定它最终覆盖哪些具体类型。为什么不允许比较函数反而是优点规则刻意回避函数比较换取的是语言规则的简单与演进安全。设想允许比较两个函数两个实现都不实现某函数、而是继承接口默认实现时编译器可以判定二者相等。但这会埋下演进隐患——一旦有人把接口中的默认实现复制到某个impl里行为没有任何变化接口却可能因此从相等变成不相等。对当前识别的用例来说CommonTypeWith这样的纯值接口已经足够未来若出现新用例再重新考虑函数比较也不迟。编译器如何落地这条规则impl声明阶段的处理在语义检查器 toolchain/check/handle_impl.cpp 中final修饰符从关键字修饰符集合读取约 L233bool is_final introducer.modifier_set.HasAnyOf(KeywordModifierSet::Final);并写入SemIR::Impl的is_final字段。同时有配套的约束检查final impl不允许出现在match_first优先块中编译器会给出诊断FinalImplInMatchFirstfinal implinmatch_firstblock并提示可以将整个match_first块标记为final来代替约 L328-L337。此外match_first块自身也有match_first_is_final标记参与最终性判定。查找阶段的最终见证toolchain/check/impl_lookup.cpp是规则落地最核心的文件其中体现了几个关键机制TreatImplAsFinal约 L232-L237impl在自己的定义体内进行查找时会被当作final处理以支持final impl内部关联常量的符号化使用仅最终查找final-only lookup当查询不是具体类型时例如泛型单态化之前的查询只搜索效果上final的impl因为只有它们能提供稳定的见证witness。源码注释明确写道在单态化中我们只想要 final 见证查询是具体的可以找所有 impl否则只要效果上的final impl约 L1253-L1257查询投毒poisoning源码注释指出记录找到一个 final impl 见证的查询之后写入会匹配同一查询的final impl是非法的约 L1134-L1135同时TreatImplAsFinal为真的 impl 不需要投毒避免符号化推导死循环泛型final impl存在时推导其结果参数可能陷入循环工具链通过impl_lookup_no_symbolic_final_lookups计数器临时禁止符号化 final 查找来打破环约 L342-L361。测试用例如何验证规则边界仓库用文件测试file_test覆盖了大量正反用例集中体现在 toolchain/check/testdata/impl/lookup/impl_overlap.carbon。单独运行该测试bazel test //toolchain/testing:file_test --test_arg--file_teststoolchain/check/testdata/impl/lookup/impl_overlap.carbon导出完整输出可用bazel run //toolchain/testing:file_test -- --dump_output --file_teststoolchain/check/testdata/impl/lookup/impl_overlap.carbon值得注意的用例包括两个final impl在match_first块之外重叠 → 报错诊断FinalImplOverlapsSameFile同文件与FinalImplOverlapsDifferentFile跨文件。例如final impl forall [T: type] T as Z(T)与final impl forall [T: type] T as Z(())重叠即被拒绝不重叠的多个final impl→ 合法如final impl D(()) as Z与final impl D({}) as Z互不重叠可以共存非final impl被final impl完全覆盖 → 报错诊断ImplFinalOverlapsNonFinalimplwill never be used提示声明了final impl的位置部分重叠的非final impl→ 合法这正是本提案的成果用例partial_overlap_type_structure_of_non_final_impl.carbon中blanket 实现impl forall [T: type] T as Z(T)与final impl C as Z(C)、impl D as Z(D)部分重叠注释明确写着 Partially overlaps the blanket impl, which is fine.互相各在某维度更具体的两个final impl→ 报错用例fail_final_overlap_where_each_is_more_specific_than_the_other.carbon展示了对C(()) as Z(())查询各自更具体但互相重叠的两个final impl会被诊断final impl放置位置受限诊断FinalImplInvalidFile——final impl只能出现在包含接口定义或Self根类型定义的文件中保证编译器可以本地检查是否有更高优先级的实现会取代它对应设计文档 docs/design/generics/details.md 中 Libraries that can contain afinalimpl 一节。min_prelude 测试骨架如 toolchain/testing/testdata/min_prelude/parts/default.carbon同样大量使用final impl构造最小标准库环境进一步佐证该语法在真实编译流程中的可运行性。真实用例标准库 prelude 中的final impl规则并非纸上谈兵Carbon 自带的标准库 prelude 就大量使用final implcore/prelude/default.carbon 中DefaultOrUnformed接口的默认实现是 blanketfinal impl约 L46-L50其函数体通过T.(Default.Op)()委托给Default接口随后才是一条基于UnformedInit的非 final 兜底实现约 L52-L53——两条实现形成精心编排的优先级关系core/prelude/types/cpp/int.carbon 为CppCompat各整数类型声明了大量final implImplicitAs、As、EqWith、OrderedWith等锁定 C 互操作整数类型的转换与比较行为不可被特化core/prelude/types/char.carbon 中也有针对AnyInt As(u8)等约束的final impl forall。注意一个容易混淆的点final impl自身可以包含函数成员如DefaultOrUnformed的fn Op()。本提案的限制只作用于试图与final impl重叠的普通impl——它只有在接口完全不含函数成员时才可能达成一致。DefaultOrUnformed这类带函数的final impl依然是合法的只是不允许再有重叠实现去特化它。备选方案与取舍提案在 proposals/p002868-allow-overlap-with-a-final-impl-if-identical.md 中详细评估了四条备选路线允许带函数成员的接口比较相等存在若干编译器可验证函数相同的特例如都继承接口默认实现但会造成前述复制定义即改变相等性的演进隐患。简单规则完全不比较函数对当前用例已足够把关联常量标为final而非整个impl关联常量没有函数那样的相等性难题看起来更聚焦。但即便引入final 关联常量本次提案对CommonTypeWith的核心改动仍然需要只是收窄到那些声明为final的关联常量上。因此该特性可能在未来有需求时补上——这与 proposals/p000983-generics-details-7-final-impls.md 中讨论该问题时的立场一致允许类型不等式约束让更特化的实现显式排除重叠情形。缺点明显更特化的实现必须知道final impl的存在往往在编译失败后才被动发现、代码变得冗长且附加无价值条件、现有类型相等性机制无法在泛型代码中一般性地证明两个类型不等在重叠区域让final impl优先于更具体的impl虽可修复问题但属于更大的设计变更当前没有充分理由引入。设计理由与后续演进提案明确表示刻意保持语言小巧用一条简单规则只解决唯一识别出的用例不多不少。它同时服务于 docs/project/goals.md 中语言工具与生态以及代码易读、易理解、易编写两大目标——因为最终靠的是 Carbon 对软件与语言演进的承诺随需求变化更新方案而不是提前为尚未出现的问题过度设计。从仓库现状看该规则仍在持续演进设计文档 docs/design/generics/details.md 在final impl一节留有 TODO指出后续提案 proposals/p005337-interface-extension-and-final-impl-update.md#5337进一步放宽了重叠规则而 proposals/p007493-disallow-impl-in-match-first-twice.md#7493则约束了match_first块的重复impl。这说明 P2868 确立的重叠必须一致原则已成为后续泛型特化相关设计共同遵循的基线。总结提案 P2868 为 Carbon 泛型系统补上了final impl规则的最后一块拼图重叠本身不是罪不一致才是。通过将一致限定为值相等、函数永不比较相等Carbon 在保持编译器实现简单、语言规则可判定的前提下让CommonTypeWith这类纯关联常量接口可以放心地在Vec等容器类型上递归组合实现同时完整保留了final impl带来的不可特化类型推导收益。结合 toolchain/check/impl_lookup.cpp 的查找实现与 toolchain/check/testdata/impl/lookup/impl_overlap.carbon 的系统性测试读者可以清晰看到一条语言特性从设计提案、语言规范到编译器落地与验证的完整链路。【免费下载链接】carbon-langCarbon Languages main repository: documents, design, implementation, and related tools. (NOTE: Carbon Language is experimental; see README)项目地址: https://gitcode.com/GitHub_Trending/ca/carbon-lang创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门