Carbon 泛型提案解析:用 `where` 约束在 `impl` 中指定关联常量

Carbon 泛型提案解析:用 `where` 约束在 `impl` 中指定关联常量 Carbon 泛型提案解析用where约束在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导读本篇文章围绕 Carbon Language 泛型设计中的一份关键提案proposals/p001013-generics-set-associated-constants-using-where-constraints.md对应 PR #1013展开讲解 Carbon 如何统一“在实现impl中为接口的关联常量associated constant与关联类型赋值”的语法从impl块内部的let声明迁移为impl声明头部的where子句。读完本文你将理解该语法改动的动机let语义不一致、前向声明与 API 文件的约束、消除冗余、新旧写法对照、备选方案取舍以及这一设计在当前仓库源码标准库 prelude、泛型设计文档、check 阶段实现中的落地证据并了解其后续演进方向。背景let一词多义引发的语义分歧let的四种使用场景在提案提出之前Carbon 中let关键字被用于多种互不相同的场景使用场景语义特征在接口中声明关联常量或关联类型泛型语义按类型擦除erased处理在impl块中定义关联常量或关联类型直接使用指定的具体值而非其类型在函数体内定义局部常量普通局部常量定义类的常量类作用域常量除impl块这一场景外其余三种let的语义大体一致——类似于把值传入函数通过类型决定该名字如何被合法使用其中会伴随对具体值的一定擦除。提案 #950 引发的语义割裂proposals/p000950-generics-details-6-remove-facets.mdPR #950Generics details 6: remove facets带来了两项关键变化impl块中let的类型部分不再“承载语义”其唯一合法的类型只允许是auto或接口中原本声明的对应类型impl块中的let不再执行类型擦除函数体内的泛型let语句有了明确语义可根据声明的类型决定是否擦除。再结合接口中let给出擦除后的类型archetype原型三者叠加后impl块中let的含义与其他使用let的场景产生了明显的不一致——这正是提案 #1013 想要解决的痛点。前向声明的需求暴露出结构性缺陷另一个现实需求是即便在 API 文件中只做前向声明forward declaration也希望能在其中指定该实现要为关联常量/关联类型提供的值。原因在于只查看 API 文件的客户端在执行类型检查时必须知道这些值但这些客户端并不需要看到impl的完整定义体。这暗示着这些“赋值”应当被声明在定义块的花括号{ ... }之外而不是藏在impl定义内部。此外where子句在泛型的其他上下文中本就是指定关联常量/关联类型取值的一种途径let再承担同样职责就形成了语法冗余。提案核心impl头部的where子句改动要点提案的改动非常集中在impl声明中使用where子句来指定关联常量和关联类型的取值取代原先写在impl定义块内部的let声明。效果上这相当于从impl块中移除let声明允许一个impl声明实现的是约束表达式constraint expression而不再局限于简单的接口或命名约束named constraint。新旧语法对照改动前let声明形式class Foo { impl as Bar { let X:! Type T; // ... 其他成员定义 } }改动后where约束形式class Foo(T:! Type) { impl as Bar where .X T { // ... 成员定义不再包含 let X 声明 } }其中.X是对接口Bar中关联常量X的成员引用rewrite constraint 的左值 T给出其取值。多个关联常量通过and连接例如impl as Bar where .X T and .Y U { ... }这种写法同时覆盖了关联类型与关联常量——在 Carbon 中关联类型本质上是值为类型type的关联常量因此统一由where约束赋值。与约束上下文的语法统一这一设计的另一层好处是语法与“在泛型签名中书写约束”完全对齐。泛型参数同样使用where与andfn FindFirstPrimeT: Container where .Element i32 - Optional(i32);实现上下文与约束上下文共用同一套where ... .成员 值 ... and ...语法二者之间可以无障碍复制粘贴这也正是提案在“备选方案”一节强调的取舍理由。设计文档的同步更新提案 #1013 被接受后同步更新了以下泛型设计文档docs/design/generics/overview.md泛型概览docs/design/generics/terminology.md泛型术语docs/design/generics/details.md泛型细节以 docs/design/generics/overview.md 的“Constraints”一节为例当前版本明确写道Constraints are also used when implementing an interface to specify the values of associated constants.并给出如下示例Stack接口含关联类型ElementTypeclass Vector(T: Movable) { extend impl as Stack where .ElementType T { ... } }这与提案描述的语法完全一致印证了该提案已正式落入设计并保持至今。源码中的落地证据标准库 preludewhere .Result Self提案语法的直接应用可以在标准库中找到大量实例。以 core/prelude/operators/arithmetic.carbon 为例算术运算符接口AddWith声明了关联类型Resultinterface AddWith(Other: type) { let Result: type; fn Op(self, other: Other) - Result; }而内建字面量类型实现该接口时正是用impl ... where .Result ...的形式指定关联类型impl IntLiteral as AddWith(Self) where .Result Self { fn Op(self, other: Self) - Self int.sadd; } impl CharLiteral as SubWith(Self) where .Result IntLiteral { fn Op(self, other: Self) - IntLiteral char_literal.sub_char; } impl FloatLiteral as Negate where .Result Self { fn Op(self) - Self float.negate; }同样的模式遍布 core/prelude/operators/bitwise.carbon如impl IntLiteral as BitAndWith(Self) where .Result Self、impl type as BitAndWith(Self) where .Result Self等文件。可以推断只要接口声明了关联类型其impl几乎都会以where .关联类型 具体值的形式给出取值这正是提案语法在真实代码库中的主流用法。标准库 preludewhere与and的多约束组合多约束场景可参考 core/prelude/iterate.carbon。Iterate接口同时声明了ElementType与CursorType两个关联类型interface Iterate { let ElementType: Copy Destroy; let CursorType: Destroy; fn NewCursor(self) - CursorType; fn Next(self, cursor: CursorType*) - Optional(ElementType); }数组类型的实现用where ... and ...一次性指定两个关联类型impl forall [T: Copy Destroy, N: IntLiteral] array(T, N) as Iterate where .ElementType T and .CursorType i32 { fn NewCursor(unused self) - i32 { return 0; } fn Next(self, cursor: i32*) - Optional(T) { ... } }注意这里还出现了参数化implforall [...]与where约束的组合实现主体内直接以CursorType、ElementType这些名字引用被赋值的关联类型无需在定义块内再写let。这正是“移除impl块内let声明”后的自然写法。check 阶段的实现支撑从编译实现看关联常量的约束在语义检查阶段被建模为 “rewrite constraint”重写约束。toolchain/check/impl.cpp 中CheckConstraintIsInterface负责验证impl所实现的约束是否为接口或具体化接口SpecificInterface并处理impl的接口匹配逻辑同文件中存在对rewrite_constraints的迭代处理。更详细的重写约束解析规则如多个重写之间的顺序、歧义处理、环检测可参见 docs/design/generics/appendix-rewrite-constraints.md。从源码结构可以推断impl声明头部的where子句会在 check 阶段被解析为对关联常量的重写约束后续在impl查找与约束满足性检查中参与类型推导与一致性验证这也是提案所设计语义“指定值而非声明局部名字”的实现基础。目标依据为何值得这样改提案从 Carbon 项目目标出发给出两条理由简化推进 docs/project/goals.md 中 “Code that is easy to read, understand, and write” 的目标——尤其是“规范简单”与“实现简单”。移除impl块内let的特殊语义让语言模型更简洁。“单一做事方式”原则这是 docs/project/principles/one_way.md 所倡导的 “prefer providing only one way to do a given thing” 的典型应用——关联常量的取值统一由where约束指定消除双轨制。备选方案回顾方案一维持现状Status quo提案讨论过保留let写法的可能性并识别出两个隐患约束弱化问题由于接口可以给关联常量提供默认值把一个impl块中的“类型表达式”复制到函数签名的约束中得到的约束可能比该impl实际交付的能力更弱例如默认值掩盖了真实实现特化导致的“看似满足”问题由于impl的特化specialization可能改变关联常量的取值一个类型可能看起来实现了某个指定了关联常量取值的约束实际却不一定满足。提案原文给出如下示例interface Bar { let X:! Type; } class Foo(T:! Type) { impl as Bar where .X T { ... } }表面上看Foo(T)满足约束Bar where .X T但针对某些特定T值的特化版本可能把.X设成了不同的值。提案团队的评估是在实践中这一行为不会让开发者感到意外因此未将其视为阻断性问题。方案二with 逗号语法另一种被讨论的备选方案是既然这是在“赋值”而非“约束”就应使用与约束语法不同的关键字组合——用with替代where用逗号,替代and来连接多个子句。最终被否决的理由不希望存在两套“非常相似却又不同”的语法徒增学习成本与记忆负担在约束上下文与实现上下文之间复制粘贴时统一语法有实际收益。未来工作API 文件中的前向声明提案为一项后续能力铺平了道路在 API 文件中单独声明“某类型实现了某接口”而不必同时给出impl的完整定义包括内部impl。这一特性还依赖两个前置议题的落地proposals/p000875-principle-information-accumulation.md原则信息累积对应的问题 #472PR #875 的后续决议。一旦支持“只声明、不定于”还会出现一个随之而来的开放问题前向声明impl时写的where约束在后续定义该impl时是否必须重复提案原文给出了两种可能性的对照class Vector(T:! Type) { impl as Container where .Element T and .Iter VectIter(T); } // 大概率允许成员函数定义中不再重复约束 fn Vector(T:! Type).(Container.Begin)[me: Self]() ... // 或许允许定义块中不再重复 .Element 和 .Iter 的约束 class Vector(T:! Type) { impl as Container { fn Begin[me: Self]() ... } }总结提案 #1013 是 Carbon 泛型设计演进中一个“以简驭繁”的典型改动通过让impl声明头部支持带where子句的约束表达式统一了关联常量赋值的唯一语法入口消除了let在impl中的特例语义并为 API 文件中的impl前向声明扫清了结构障碍。该设计已完整落地于当前仓库——既体现在 docs/design/generics/overview.md 等设计文档中也大量实践于 core/prelude 标准库的运算符与迭代器实现里并由 toolchain/check/impl.cpp 的重写约束检查逻辑在编译器中得到实现支撑。对学习 Carbon 泛型或跟踪其语言演进的读者而言理解这份提案是掌握“约束、关联常量、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),仅供参考