资讯详情

Carbon 泛型系列十:interface-implemented requirements(接口实现要求)的设计演进与 observe 证明机制

📅 2026/9/11 11:41:33 | 华诺云谱 👁 阅读
Carbon 泛型系列十:interface-implemented requirements(接口实现要求)的设计演进与 observe 证明机制
Carbon 泛型系列十interface-implemented requirements接口实现要求的设计演进与 observe 证明机制【免费下载链接】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 的提案 p001088: Generic details 10: interface-implemented requirements 为核心系统讲解 Carbon 泛型系统中接口如何要求其他类型而非仅Self也实现某个接口这一能力的语法、约束、局部可检查性边界以及配套的observe声明证明机制。读完本文你将掌握require type impls facet type的完整写法与合法性规则、带where约束的要求为何难以局部验证、以及如何用observe ... impls向编译器提供类型实现了某接口的显式证明从而避免不可终止的递归搜索。一、问题背景从接口要求自身到接口要求其他类型Carbon 的泛型设计文档 docs/design/generics/details.md 很早就支持一种能力一个接口可以要求实现它的类型即Self同时实现另一个接口。例如interface Equatable { fn Equals(self, rhs: Self) - bool; } interface Iterable { fn Advance(ref self) - bool; require impls Equatable; } fn DoAdvanceAndEqualsT: Iterable { // x 的类型 T 实现了 Iterable因此拥有 Advance。 x.Advance(); // Iterable 要求 Equatable 的实现因此 T 也实现了 Equatable。 x.(Equatable.Equals)(x); }这种能力最初由 提案 #553: Generics details part 1 引入本提案p001088正是它的续篇。p001088 提案要解决的问题是让接口可以要求除Self之外的某个类型也实现某接口。这一表达能力的直接动机来自如何为CommonTypeWith这类接口实现对称行为——该需求在 提案 #911: Conditional expressions 的讨论中被提出。同时该能力也带来两个隐忧如果接口要求带有where子句是否能在本地检查某个impl是否满足该要求是存在疑问的详见本文备选方案一节一个函数若想利用某类型因接口要求或 blanket impl 而实现了某接口这一事实可能需要编译器执行一次无法保证有界的搜索。二、提案核心为泛型设计文档新增两节内容本提案的落地方式很直接——它向 docs/design/generics/details.md 增加两个章节新增章节位置内容Interface requiring other interfaces revisiteddetails.md 第 5957 行起定义require type impls facet type语法要求非Self类型也实现接口并规定Self必须参与约束的合法性规则Observing a type implements an interfacedetails.md 第 6102 行起定义用observe ... impls声明证明某类型实现了某接口替代编译器不可控的递归搜索在语言实现层面observe声明的语法处理位于 toolchain/check/handle_observe.cppimpl声明的检查逻辑位于 toolchain/check/handle_impl.cpp 与 toolchain/check/impl_lookup.cpp读者可以结合这些实现文件印证下文语法语义。三、语法详解require type impls facet type3.1 基本形式省略类型即Self回顾 Interface requiring other interfacesdetails.md 第 1350 行起最早的语法是require impls facet type省略了类型——此时类型被隐含为Selfinterface Iterable { require impls Equatable; // ... }这表示实现Iterable的类型此处即Self必须同时实现Equatable。3.2 指定其他类型require type impls facet type与 条件一致性conditional conformance 的处理方式一致Carbon 允许在require与impls之间指定一个其他类型从而要求该类型而非Self实现接口interface IntLike { require i32 impls As(Self); // ... }含义是如果Self实现了IntLike那么i32必须实现As(Self)。再例如对称性约束——这是CommonTypeWith设计中的典型需求interface CommonTypeWith(T: type) { require T impls CommonTypeWith(Self); // ... }含义是如果Self实现了CommonTypeWith(T)那么T必须实现CommonTypeWith(Self)。这一对称关系正是 提案 #911 中条件表达式实现CommonTypeWith所需的核心能力。3.3 关键规则Self必须参与约束结构require约束并非可以任意书写。设计文档明确要求在interface或constraint定义中require type impls facet type里的type必须是Self本身或者以Self作为参数化类型/接口的参数。也就是说Self必须出现在任何能满足该require的impl的类型结构中。设计文档给出了完整的允许/禁止清单// ✅ Allowed: require impls Equatable // ✅ Allowed: require Self impls Equatable // ✅ Allowed: require Vector(Self) impls Equatable // ✅ Allowed: require i32 impls CommonTypeWith(Self) // ✅ Allowed: require impls CommonTypeWith(Self) // ✅ Allowed: require Self impls CommonTypeWith(Self) // ❌ Error: require i32 impls Equatable // ❌ Error: require i32 impls Equatable where .Result Self // ❌ Error: require T impls Equatable 当 T 是接口的参数时这条限制的深层原因是相干性coherence与搜索的可行性如果任意接口里都能写require i32 impls Equatable那么编译器在回答i32实现了哪些接口时就必须搜索所有已导入的接口定义——类型的事实集合将取决于导入了哪些接口从而破坏 相干性details.md 对 coherence 的讨论见第 59966020 行。强制Self参与类型结构能让编译器知道该去哪里查找关于某类型的事实。四、如何满足一个接口实现要求当实现一个带require ... impls要求的接口时该要求必须由以下三者之一满足一个已导入库中的实现一个同一文件中的实现该impl声明自身约束中的事实。实现带要求的接口本质上是承诺该要求会被满足——这类似于 impl 的前向声明区别在于满足要求的实现可以更宽泛而不必完全匹配。// Iterable 要求 Equatable因此本文件中必须存在 // Vector(i32) as Equatable 的某个实现。 impl Vector(i32) as Iterable { ... } fn RequiresEquatableT: Equatable { ... } fn ProcessVector(v: Vector(i32)) { // ✅ 允许已知 Vector(i32) 实现了 Equatable。 RequiresEquatable(v); } // 满足 Vector(i32) 必须实现 Equatable 的要求 // 因为 i32 impls Equatable。 impl forall [T: Equatable] Vector(T) as Equatable { ... }在某些情况下要求可以被实现自身平凡地满足例如impl forall [T: type] T as CommonTypeWith(T)——它自己就实现了CommonTypeWith(T)的要求。更精巧的用法是通过impl声明中的约束来满足要求class Foo(T: type) {} // 允许因为对于所有使用此 impl 的类型 T // 已知必然存在 impl Foo(T) as Equatable // 尽管既没有导入的实现也没有本文件内的实现。 impl forall [T: type where Foo(T) impls Equatable] Foo(T) as Iterable {}这可以用于为已满足Iterable要求的类型反向提供Equatableclass Bar {} impl Foo(Bar) as Equatable {} // 通过 Iterable 的 blanket impl // 使 Foo(Bar) impls Iterable 成立。五、带where约束的要求局部可检查性的难题当接口实现要求携带where子句时满足它就困难得多。考虑接口B要求接口A也被实现interface A(T: type) { let Result: type; } interface B(T: type) { require impls A(T) where .Result i32; }对一组类型而言只有当下述条件同时成立时B的实现才有效存在一个可见的A(T)实现且其.Result关联 facet 被赋值为i32。但这并不充分——除非A的实现不可被特化即要么被标记为final要么本身就是 非参数化实现。原因在于其他库中的实现不会让A为更少的类型实现但可能让.Result有不同赋值从而悄悄推翻本地检查得出的结论。这一不充分性正是 提案 p001088 备选方案一节重点分析的场景详见本文第七节。六、observe声明把搜索变成证明6.1 动机避免不可终止的递归搜索本提案选择的设计方向是由源码提供任何需要递归搜索才能得出的事实证明。observe声明既可以证明两个类型相等从而免去显式 cast也可以证明一个类型实现了某接口——在编译器无法自行推导出该事实的场合使用。设计文档 Observing a type implements an interfacedetails.md 第 6102 行起给出了三种典型场景。6.2 场景一观察接口要求的传递链类型检查阶段做impl校验时Carbon 只考虑该类型已知实现的接口的直接要求。observe ... impls声明可以把一个直接要求加入待考虑的直接要求集合从而由开发者手工搭建出要求链条的证明interface A { } interface B { require impls A; } interface C { require impls B; } interface D { require impls C; } fn RequiresAT: A; fn RequiresCT: C; fn RequiresDT: D { // ✅ 允许D 直接要求 C 被实现。 RequiresC(x); // ❌ 非法D 与 A 之间没有直接联系。 // RequiresA(x); // T impls D且 D 直接要求 C 被实现。 observe T impls C; // T impls C且 C 直接要求 B 被实现。 observe T impls B; // ✅ 允许T impls B且 B 直接要求 A 被实现。 RequiresA(x); }注意observe语句不参与代码生成阶段的 impl 选择。为维持 相干性同一 (类型, 接口) 对在任何上下文中都必须选择同一个 impl终结规则termination rule 负责在编译器无法确定要选择的 impl 时决定何时判定编译失败。6.3 场景二观察 blanket impl 声明observe ... impls也可以用来断言某类型因其已满足的条件而经由 blanket impl 声明 实现了某接口。没有observe声明时Carbon 只会使用直接被满足的 blanket implinterface A { } interface B { } interface C { } interface D { } impl forall [T: A] T as B { } impl forall [T: B] T as C { } impl forall [T: C] T as D { } fn RequiresDT: D; fn RequiresBT: B; fn RequiresAT: A { // ✅ 允许存在 B 对实现 A 的类型的 blanket 实现。 RequiresB(x); // ❌ 非法对实现 A 的类型 T没有 D 的实现。 // RequiresD(x); // 存在 B 对实现 A 的类型的 blanket 实现。 observe T impls B; // 存在 C 对实现 B 的类型的 blanket 实现。 observe T impls C; // ✅ 允许存在 D 对实现 C 的类型的 blanket 实现。 RequiresD(x); }发生错误时高质量的实现应当做一次更深的搜索尝试找出要求与 blanket impl 的链条并在能找到解时向开发者建议可编译的observe声明。6.4 场景三观察等于某个已知实现接口的类型observe ... 形式见 Observe declarationsdetails.md 第 3210 行起可以与observe ... impls组合证明某类型因等于另一个已知实现接口的类型而实现该接口interface I { fn F(); } fn G(generic T: I, generic U: type where .Self T) { // ❌ 非法U 没有 I 的实现。 U.(I.F)(); // ✅ 允许U 通过 T 实现了 I。 observe U T impls I; U.(I.F)(); // ❌ 非法U 并不扩展 I成员访问不可用。 U.F(); }一个observe声明中允许有多个子句例如observe A B C impls I;。此外observe .. .. impls声明可以包含立即满足impls约束的无关联值这与等价链至多包含一个无关联值的通用限制略有不同如 details.md 第 32743293 行的Core.AddWith示例所示。七、备选方案为什么最终选择收紧 证明p001088 提案在权衡阶段考虑过两个更宽松的替代方案并记录了它们被拒绝的理由。7.1 备选一对带where子句的要求放宽检查一个可能的做法是只要约束仍然被满足就允许可被特化的实现来满足带where约束的要求。但问题在于——这不是一个可在本地检查的条件。提案给出了一个经典的跨包冲突例子涉及四个包包Interfaces定义两个接口A与B其中B带where要求package Interfaces api; interface A(T:! Type) { let Result:! Type; } interface B(T:! Type) { impl as A(T) where .Result i32; }包Param定义类型P并给出A(P)与B(P)的 blanket implpackage Param api; import Interfaces; class P {} external impl [T:! Type] T as Interfaces.A(P) where .Result i32 { } // 疑问这个 Interfaces.A(P) 的 blanket impl // 是否足以支撑下面的 Interfaces.B(P) blanket impl external impl [T:! Type] T as Interfaces.B(P) { }包Class定义类型C并用通配 implwildcard impl实现Apackage Class api; import Interfaces; class C {} external impl [T:! Type] C as Interfaces.A(T) where .Result bool { }包Main尝试把以上三个包组合使用package Main; import Interfaces; import Param; import Class; fn FV:! Interfaces.B(Param.P); fn Run() { var c: Class.C {}; // Class.C 是否实现了 Interfaces.B(Param.P) F(c); }分析包Param为任意T提供了Interfaces.B(Param.P)的实现理应涵盖T Class.C。此时Interfaces.B的要求是T Class.C必须实现Interfaces.A(Param.P)这成立并且Class.C.(Interfaces.A(Param.P).Result)必须是i32。借助Param中的 blanket impl这一点本可以成立——但包Class中的通配 impl 优先级更高它把.Result赋成了bool。结论这类问题只有在单态化monomorphization阶段才会暴露并可能使两个各自独立工作的库组合后互不兼容。这是足够严重的缺陷因此提案选择先接受允许本地检查的收紧限制。设计文档也指出开发者在这种场景下本来就倾向于把参数化实现声明为final尽管final自身也有 限制。7.2 备选二不要求observe ... is声明让编译器自行搜索另一个备选是让 Carbon 编译器自行搜索由某类型实现的一组接口传递蕴含出的全部接口。但问题同样明显——无法界定搜索深度。事实上这种搜索一旦与条件一致性结合会使该类型是否实现了该接口这个问题变得不可判定Rust 类型系统已被证明在此意义上是图灵完备的。虽然 无环规则acyclic rule 可能让 Carbon 的 blanket impl 场景避开这个问题但该规则并不适用于接口要求。更深层地这与 终结规则details.md 第 5005 行起呼应判断一组 impl 声明是否终止等价于停机问题整体不可判定。Carbon 采用近似保证终止的策略——类型在同一个 impl 上不能变得严格更复杂复杂度的度量方式是统计各基础类型的出现次数。接口实现要求如果允许任意搜索将直接与这套可控的近似策略冲突。因此最终方案是要求源码显式提供证明observe。这样递归搜索的开销只在出现编译错误时才会发生一旦搜索成功其结果被复制回源码此后无需重复搜索——这正是 Fast and scalable development 目标的具体体现。八、设计目标呼应为什么要这个表达能力提案在 Rationale 一节明确将其与 Carbon 的两项目标挂钩Language tools and ecosystem接口实现要求的表达力源于对CommonTypeWith这类接口实现对称行为的讨论即 提案 #911 的配套需求。有了require T impls CommonTypeWith(Self)库作者可以表达类型间对称关系为条件表达式等语言特性提供坚实的类型基础。Fast and scalable development要求源码为需要递归搜索的事实提供observe证明把搜索代价限制在编译错误场景成功结果固化进源码后无需重复计算利于大型代码库的编译可扩展性。九、从源码验证observe在工具链中的落点语法/语义处理handle_observe.cpp 是observe声明在 checker 中的入口与之配套的observe语义定义位于 handle_observe.cpp 对应的编译单元及 check.h 中的上下文接口。impl 查找与相干性impl_lookup.cpp 负责 (类型, 接口) 对的实现查找是observe 不改变代码生成期 impl 选择这一语义约束的实现基础handle_impl.cpp 处理impl声明本身。核心接口标识core_identifier.def 定义了编译器内部对核心接口如Core.AddWith的标识与observe ... impls例子中引用的接口对应。实现侧测试数据集中在 toolchain/check/testdata 目录内含 977 个.carbon测试用例其中 generics 相关用例可用于对照本文示例的实际编译行为。十、小结p001088 提案为 Carbon 泛型体系补上了接口要求其他类型实现接口这块拼图语法require type impls facet type省略类型时默认为Self支持如require i32 impls As(Self)、require T impls CommonTypeWith(Self)的表达约束Self必须参与约束的类型结构以维持相干性与搜索可行性证明机制observe ... impls支持三种证明方式——接口要求链、blanket impl 链、以及类型相等传递边界带where的要求与可特化实现组合不可局部检查故被收紧编译器的全局搜索因不可判定性被禁止改为由源码提供证明。这套表达力 本地可检查 显式证明的组合拳正是 Carbon 在泛型设计上兼顾能力与工程可扩展性的典型取舍。延伸阅读设计全文Generics details重点章节Interface requiring other interfaces、Interface requiring other interfaces revisited、Observing a type implements an interface关联提案Generics details part 1接口要求能力的首次引入、Conditional expressionsCommonTypeWith对称性的动机来源相关概念泛型术语表coherence、conditional conformance 等定义、项目目标【免费下载链接】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),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。