Carbon 语言约束必须使用 `Self`:解析 p002376 提案与 `where` 子句的消歧设计
Carbon 语言约束必须使用Self解析 p002376 提案与where子句的消歧设计【免费下载链接】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导读proposals/p002376-constraints-must-use-self.md是 Carbon Language 泛型Generics设计体系中的一项关键约束规则提案在interface或constraint定义中impl as约束必须隐式或显式地提及Selfwhere子句必须直接引用.Self或通过.Foo之类的设计符designator间接引用。本提案直接回答了泛型约束到底约束谁这一语义问题并为编译器在 impl 查找impl lookup时将搜索范围收敛到有限集合提供了理论基础。读完本文你将理解这条规则为何存在、如何书写合法约束、如何把非法约束改写为合法形式以及它如何在当前 Carbon 设计文档与编译器源码中落地。背景where子句的四种歧义解读该提案的出发点来自 Carbon Explorer 实现约束时遇到的一个真实例子。考虑如下声明interface A {} interface B {} external impl forall [T:! Type] T as A where T is B {}这里的where T is B存在多种相互矛盾的解读而语言设计者无法仅凭语法判断作者的真实意图它可能等价于external impl forall [T:! Type where .Self is B] T as A {}即只为满足B约束的那些T引入A的实现它也可能等价于external impl forall [T:! Type] T as A {}但要求对全体T都存在B的实现缺少时整体非法它还可能等价于external impl forall [T:! Type] T as A B {}即顺带为所有T引入B的实现或者它本身就应当被判定为非法。一个where子句出现四种含义说明这种写法已经破坏了一段代码只有一种含义的底线。让这种构造直接非法可以强制代码改写为含义更清晰的形式这比试图在文档中逐个解释每种歧义要可靠得多。另一个被讨论的例子是约束修改其他类型带来的意外fn FA:! Type, B:! Type, C:! Type where A B;类型A与B之间的关系由第三个类型参数C的声明来建立这既反直觉也难以维护。提案认为更好的写法是把约束移动到它所约束的类型上fn FA:! Type, B:! Type where A .Self, C:! Type;这样A与B的关系就从各自的声明中自然建立而不是被后面某个无关声明事后修改。结论先行三条核心理由综合各类案例后Carbon 团队认为where子句应当被限定为对被约束类型自身的约束理由有三单一方式原则Carbon 遵循 One Way 原则同一件事只提供一种写法。团队相信所有不满足本限制的例子都能改写成满足限制的形式从而消除多种等价写法之间的选择负担。歧义更少改写之后代码的语义尤其是impl声明的适用范围不再有上文四种解读之间的摇摆。约束在类型声明完成时即完整一个类型所携带的约束应当随其声明一并封闭而不是在文件后续位置被追加修改。对interface或constraint内部的impl as约束施加必须涉及Self的限制还带来两个编译器层面的关键收益搜索范围有限编译器在 impl 查找时只需要查看与目标类型Self相关的那些定义从而把搜索限制在有限数量的定义集合内避免 coherence 问题若没有该限制一个类型实现了哪些接口会随导入的接口定义不同而变化破坏实现的一致性coherence。更进一步的推论是该限制允许 interface 与命名约束在不完整incomplete状态下被使用从而支持包括自引用在内的循环引用场景。逻辑链条为我们希望限制何时需要从某个约束中获取信息于是不需要查看约束的情形变多这些情形下约束被允许保持不完整。允许约束不完整的具体条件由提案 p002347: What can be done with an incomplete interface 规定而该提案的规则正是让那些规则可实现的基石。提案正文两条具体规则规则一where子句必须使用设计符where子句中的约束必须引用一个设计符要么是.Self要么是某个成员名.Foo。设计符可以直接使用也可以作为某个类型、接口或命名约束的实参出现在where子句中。提案给出了三个规范示例Container where .ElementType i32—— 通过成员设计符.ElementType约束关联常量Type where Vector(.Self) is Sortable—— 把.Self作为Vector的类型实参再约束Vector(.Self)必须可排序Addable where i32 is AddableWith(.Result)—— 把成员设计符.Result作为AddableWith的类型实参。语法说明该提案撰写时约束关系使用is关键字如T is B。当前仓库的设计文档 Generics: Details 已随提案 p002483: replace keywordiswithimpls改用impls表达约束关系例如Vector(.Self) impls Sortable。下文示例均采用当前语法。规则二interface/constraint内的impl as必须涉及Self在接口与命名约束定义中出现的impl as声明必须始终与Self相关。Self可以出现在三个位置作为被实现类型as左侧省略类型时即隐式Selfimpl as ...显式写出impl Self as ...作为类型实参impl Vector(Self) as ...作为as右侧接口/约束的类型实参例如impl Vector(i32) as AddWith(Vector(Self))作为as右侧接口/约束的普通参数例如impl T as Bar(Self)。换句话说Self必须出现在impl声明中左、右两侧任意一侧的某个参数位置但不能完全缺席。递归性与有限搜索当编译器判断某个约束是否暗示某个 impl 存在时唯一需要查看的位置就是涉及该 impl 目标类型Self的那些定义并且这一规则递归适用被引用的约束中的约束同样只需查看涉及Self的部分。这意味着编译器永远不需要去翻查那些不涉及目标类型的、被前置声明或以其他方式不完整的约束。这直接解决了一个 impl 查找的深层问题通过接口可以到达的约束集合在理论上可能是无限的接口 A 引用接口 BB 又引用 A……但在这条规则约束下实际需要考虑的只是其中涉及Self的有限子集从而保证查找过程可以终止。设计文档中的落实提案的Details章节指出Generics: Details 设计文档已随本提案更新具体落实在 “Constraints must use a designator” 小节。该小节给出了一组非法/合法对照示例完整继承了提案的规则并补充了更多细节// ❌ Error: where A B 未使用 .Self 或设计符 fn FA: type, B: type, C: type where A B; // ✅ Allowed fn FA: type, B: type where A .Self, C: type;该规则同样适用于impl声明中的where子句// ❌ Error: where T impls B 未使用 .Self 或设计符 impl forall [T: type] T as A where T impls B {} // ✅ Allowed把约束移到类型参数声明上 impl forall [T: type where .Self impls B] T as A {} // ✅ Allowed直接以 B 作为 T 的 facet 类型 impl forall [T: B] T as A {}设计文档还强调了一个容易被忽略的细节多个约束组合在一个where表达式中时每个约束都必须各自包含设计符。例如// ❌ Error: where C impls A 未使用 .Self 或设计符 fn F(generic T: type where C impls (A B(.Self))); // 等价于 (type where C impls A) (type where C impls B(.Self)) // 其中前一个子约束不合法而这样的组合是允许的// ✅ Allowed fn F(generic T: type where C impls A(.Self) and X .Self);关于.Self的语义设计文档的 Recursive constraints 小节补充了一个重要模型Self被视作每个接口的真实关联 facet 成员因此可以在where子句中像其他关联 facet 成员一样通过.Self来寻址。例如fn ReluT: HasAbs where .MagnitudeType .Self { // T.MagnitudeType T return (x.Abs() x) / 2; }where子句另一条相邻规则是 “Referencing names in the interface being defined”where子句中的约束只能引用本作用域中更早声明的名字这与本提案约束随声明封闭的精神一脉相承。class内impl的对应澄清设计文档在 conditional conformance 一节同时澄清class定义中的impl只能针对正在定义的那个类型。例如class Array(T: type, template N: i64) { // ❌ Illegalextend impl 之后、as 之前不允许 forall 或指定其他类型 extend impl forall [P: Printable] Array(P, N) as Printable { ... } }需要条件实现时应把impl放在类外受 orphan/coherence 规则约束或放在类内但不带extendclass Array(T: type, template N: i64) { // ✅ Allowed类作用域内不扩展 API 的 impl 允许 forall 并指定类型 impl forall [P: Printable] Array(P, N) as Printable { ... } }这与提案impl as约束必须涉及Self的思想同源类型自身的 API 与实现声明被绑定在其定义处不允许被无关声明事后改造。设计合理性论证提案的Rationale章节明确把这条规则挂在 Carbon 的三条顶层原则上单一方式原则通过削减等价约束写法的数量落实 One Way 原则代码可读性避免上文那种含混、歧义的构造支撑 代码易读、易懂、易写的目标编译性能与可扩展性把 impl 查找时需检查的约束收敛到有限集合支撑 快速、可扩展开发的目标。被否决的替代方案提案的Alternatives considered章节记录了两个方向的权衡主要替代方案不施加任何限制。团队在 2022 年 6 月与 10 月的多次#generics-and-templates讨论及开放讨论中最终否决了这一方案理由即 Problem 章节所列的三点单一方式、歧义更少、约束随声明封闭以及有限搜索与 coherence 收益。主要缺点失去为类型选择其他名称的自由。施加限制后where子句中只能以.Self指代当前类型无法再使用自定义名称。当时的顾虑是.Self可能被视为难以理解的高级特性或者书写起来更长。权衡之下团队认为确定性语义的价值高于这一点命名便利。前序提案与演进脉络本提案不是孤立的设计它建立在一系列泛型提案之上并在 Background 中列明了继承关系p000553: Generics details part 1 —— 引入接口与命名约束中的impl as限制p000818: Constraints for generics (generics details 3) —— 引入where约束p001013: Generics: Set associated constants usingwhereconstraints —— 在impl声明中用where约束指定关联常量p001084: Generics details 9: forward declarations —— 允许接口与命名约束前置声明使不完整接口超出其定义期仍可使用p002107: Clarify rules aroundSelfand.Self—— 确立Self与.Self的使用规则本提案在此基础上扩展p002347: What can be done with an incomplete interface —— 明确不完整接口/命名约束的可用规则这些规则依赖本提案方可实现。后续演进中is关键字被impls取代p002483where中的约束关系统一写作impls同时require impls语法在命名约束中承担了接口实现要求的角色。当前设计文档中可看到完整形态例如constraint VectorLegoFish { // 接口实现要求 require impls Vector; require impls LegoFish; // 名字 alias Scale Vector.Scale; alias VAdd Vector.Add; alias LFAdd LegoFish.Add; }示例出处Named constraints 小节。在编译器中的实现落点从当前仓库的编译器源码结构看本提案的语义分散在toolchain/check的多个环节impl_lookup.cpp 与 impl_lookup.h —— impl 查找的核心实现即提案中编译器只需在涉及Self的定义中查找规则的作用点handle_where.cpp ——where子句的语法处理负责解析设计符约束handle_named_constraint.cpp —— 命名约束定义的处理facet_type.cpp 与 eval.cpp —— facet 类型的求值与Self作为隐式关联成员的模型直接相关。从源码结构可以推断where子句约束、Self/.Self的解析与 impl 查找是相互耦合的子系统约束必须引用设计符的规则在语法解析与语义检查阶段执行而有限搜索则是在 impl 查找阶段受益。有兴趣深入底层的读者可以从 toolchain/check 目录入手结合toolchain/sem_ir中的类型表示追踪完整处理链路。小结Constraints must use Self提案 p002376以两条简洁的语法规则——where子句必须使用.Self/.Foo设计符、impl as声明必须涉及Self——换来了三方面收益消除where语义歧义、把 impl 查找收敛到有限集合、以及在不完整接口支持下允许循环引用。它同时是 Carbon 单一方式原则在泛型约束领域的一次具体应用。理解这条规则是读懂 Carbon 泛型where约束、条件实现conditional conformance与 impl 查找设计的前提。【免费下载链接】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),仅供参考