Carbon 语言不完全接口(Incomplete Interface)能力边界全解析:前向声明规则、允许操作与循环引用实战

发布时间:2026/9/11 20:47:37
Carbon 语言不完全接口(Incomplete Interface)能力边界全解析:前向声明规则、允许操作与循环引用实战 Carbon 语言不完全接口Incomplete Interface能力边界全解析前向声明规则、允许操作与循环引用实战【免费下载链接】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 语言泛型体系中的一个核心设计问题当一个接口interface或命名约束named constraint只被前向声明、尚未给出定义即不完全时究竟可以对它做哪些操作、不可以做哪些操作。该规则源自提案 p002347-what-can-be-done-with-an-incomplete-interface.md其最终落点为泛型设计文档 Generics details 中的 Forward declarations and cyclic references 章节。阅读本文后你将掌握前向声明与不完全实体的精确语义、可/不可用场景的完整清单含代码级示例、如何用前向声明构建图结构等循环引用、如何在接口参数列表自引用时用命名约束绕开限制以及编译器toolchain/check如何用诊断信息落实这些规则。1. 问题背景为什么要不完全接口1.1 约束constraints概念的引入提案首先明确了一个术语约定文中讨论的约束指接口interface与命名约束named constraint两类实体。接口与命名约束在这一规则下不需要区别对待——对两者采用同一套规则有助于语言保持简单与统一。从语法上看一个接口或命名约束的定义由声明 花括号{ ... }包裹的函数体构成而**前向声明forward declaration则是声明 分号;。前向声明是一种承诺被声明的实体稍后一定会被定义。从实体的首次声明可能是前向声明也可能是定义的开头部分开始直到定义结束为止该接口或实现被称为不完全incomplete**的。1.2 递归与引用环的驱动支持前向声明约束的根本动机是允许在约束被允许定义之前就使用它典型场景是递归或引用环reference cycle。例如声明一张图的边edge与节点node接口时二者天然互相引用边接口需要引用节点类型节点接口又需要引用边类型必然构成循环依赖。然而某些使用方式要求约束的定义可见才能完成检查。例如访问接口成员、验证关联常量赋值等。这些检查在某些情况下可以推迟但代价是编译器实现复杂度上升。本提案的目标就是精确划定哪些使用是允许的特别是补齐先前提案 前向声明提案 #1084Generics details 9: forward declarations未覆盖的情况。该案例如今对应仓库中的泛型设计文档 details.md 之 Forward declarations and cyclic references 章节。补充在 Carbon 语义检查器SemIR 检查阶段的实现中不完全状态与正在定义状态是区分的。见 type_completion.cpp 中DiagnoseIncompleteInterface、DiagnoseIncompleteNamedConstraint等函数is_being_defined()对应当前正在定义例如接口定义体内引用自身否则即前向声明过。2. 核心规则不完全接口能做什么、不能做什么以下规则已正式进入 docs/design/generics/details.md 的 Forward declarations and cyclic references 章节是该提案的最核心产出。2.1 前向声明接口/命名约束的总体约束一个接口或命名约束可以被前向声明但受以下规则约束定义必须与声明位于同一文件中。详情见第 4 节的备选方案讨论只有第一次声明可以带访问控制关键字如private。不完全接口或命名约束可以被用作类型、函数、接口或命名约束声明中的约束。这包括接口或命名约束内部的require声明但不包括指定关联常量值——因为那涉及对不完全约束进行名字查找。使用不完全接口或命名约束作为签名、去定义泛型函数体是非法的。使用不完全接口或命名约束作为签名、去调用泛型函数是非法的。对不完全接口或命名约束做任何名字查找都是错误。例如用MyInterface.MemberName访问接口成员、或用where子句约束成员都是非法的关于where子句见 details.md 的 where constraints 小节。2.2 允许清单✅设C为不完全接口或命名约束的名称则以下上下文合法允许的用法代码形态说明检查绑定中的约束T: CT为检查绑定checked binding约束组合C D可能因C、D冲突而无效但只有在二者都完整后才能发现要求实现interface ... { require ... impls C; }或constraint ... { require ... impls C; }实现C所隐含的内容在C完整之前不可见蕴含检查T: C且T impls CT为检查绑定组合蕴含检查T: A C且T impls CT为检查绑定包含需要T impls C的构造如T as C或U: C T前向声明实现impl ... as C;对C所有关联常量赋值正确性的检查会推迟到C完整之后2.3 禁止清单❌同样设C为不完全接口或命名约束禁止的用法代码形态原因成员访问T: C且T.X需要查看C的定义才能确认X是否存在带where的约束T: C where ...需要在C内查找成员类作用域扩展实现class ... { extend impl as C; }extend声明要求目标作用域是完整的见 member_access.md 的 extend 小节接口内扩展要求interface ... { extend require impls C; }或constraint ... { extend require impls C; }同上蕴含其他约束的检查T: C且T impls AA不同于C需要看C的定义才能知道它是否蕴含A实现定义impl ... as C { ... }关联常量赋值无法在声明时得到验证需要强调一个容易混淆的点允许impl ... as C;带分号的前向声明但禁止impl ... as C { ... }带函数体的实现定义。前者把关联常量检查推迟到C完整后者则必须在定义C之前就能完成验证因此非法。2.4 编译器如何诊断这些违规这些规则并非纸面设计而是在 Carbon 编译器检查器中有对应实现。以 type_completion.cpp 为例InterfaceIncompleteWithinDefinitioninterface is currently being defined——接口在其自身定义内被使用InterfaceForwardDeclaredHereinterface was forward declared here——用于定位前向声明的源位置NamedConstraintIncompleteWithinDefinition/ 对应前向声明诊断——命名约束的同类情况实现相关诊断ImplAsIncompleteFacetTypeDefinitiondefinition of impl as incomplete facet type——定义实现但不完全接口见 impl.cpp 中对该诊断的触发点。在检查器的集成测试中这些规则被逐条验证例如toolchain/check/testdata/interface/incomplete.carbon覆盖extend require impls A报RequireImplsUnidentifiedFacetType、接口定义体内extend require报RequireImplsIncompleteFacetType、以及impl {} as I where .T ...对不完全类型C报IncompleteTypeInConversion等场景toolchain/check/testdata/impl/incomplete.carbon验证impl {} as I {}对不完全接口 I 定义实现报错并附 interface was forward declared here 定位提示toolchain/check/testdata/impl/forward_decls.carbon验证前向声明实现的合法用法。单独运行这些测试的方式详见各测试文件头部注释bazel test //toolchain/testing:file_test \ --test_arg--file_teststoolchain/check/testdata/interface/incomplete.carbon bazel run //toolchain/testing:file_test \ -- --dump_output --file_teststoolchain/check/testdata/interface/incomplete.carbon3. 实战用前向声明构建循环引用的图结构3.1 边与节点的经典示例设计文档给出了一组完整的可编译示例见 details.md Example of declaring interfaces with cyclic references。核心思想Node接口有一个受约束为实现Edge的关联 facetEdgeTEdge接口有一个受约束为实现Node的关联 facetNodeT且二者互为对方的原始类型。这种双向约束无法直接写出于是先命名并前向声明无法直接表述的约束// 前向声明接口用于约束的参数列表 interface Edge; interface Node; // 前向声明命名约束用于接口定义 private constraint EdgeFor(N: Node); private constraint NodeFor(E: Edge); // 用命名约束定义接口 interface Edge { let NodeT: NodeFor(Self); fn Head(self) - NodeT; } interface Node { let EdgeT: EdgeFor(Self); fn Edges(self) - DynArray(EdgeT); } // 接口已定义完整此时才可以引用接口成员 // 因此现在定义命名约束是合法的 constraint EdgeFor(N: Node) { extend Edge where .NodeT N; } constraint NodeFor(E: Edge) { extend Node where .EdgeT E; }执行流程分三步前向声明接口Edge、Node用于后面约束的参数列表前向声明命名约束EdgeFor、NodeFor用于接口定义体内let ...: ...的类型约束先定义接口再回头定义命名约束——因为命名约束体extend Edge where .NodeT N涉及对Edge成员的where约束而接口只有在完整后其成员才可见、才允许被where引用。3.2 该方案的已知局限与未来方向设计文档也坦承该方案有局限以Future work标注例如在interface Node定义体内编译器只知道EdgeT可转换为type可能不足以满足作为DynArray参数的要求。若未来此问题成为痛点可能扩展不完全接口/类型的能力让上面代码无需额外私有约束即可直接写出interface Node; interface Edge { let NodeT: Node where .EdgeT Self; fn Head(self) - NodeT; } interface Node { let EdgeT: Movable Edge where .NodeT Self; fn Edges(self) - DynArray(EdgeT); }这属于设计文档中标注的未来工作方向当前并未落地。3.3 实现细节前向声明实现与where _语法除接口/约束前向声明外设计文档还配套规定了实现impl的前向声明Declaring implementations 小节要点如下实现的定义必须在同一库中可以在同一文件或声明在 API 文件、定义在 impl 文件若既有前向声明又有定义只有第一次声明必须用where子句指定关联常量赋值后续声明可用where _省略不能前向声明一个不完全接口的实现——这保证impl声明中的关联常量赋值可以在声明时被验证为满足一致性coherence同一文件内任何匹配到 impl 查找查询的impl声明必须在查询之前声明定义或前向声明均可这符合 信息累积原则。设计文档 Declaration examples 小节给出了一段完整对照示例节选展示接口前向声明、类内内联定义、where _复用等组合interface Interface1; interface Interface2; interface Interface3; interface Interface4; class MyClass; // ❌ 非法不能为不完全接口声明实现 // impl MyClass as Interface1; interface Interface1 { let T1: type; } interface Interface2 { let T2: type; } interface Interface3 { let T3: type; } interface Interface4 { let T4: type; } // 类外前向声明实现必须带关联常量赋值 impl MyClass as Interface1 where .T1 i32; impl MyClass as Interface2 where .T2 bool; class MyClass { // 内联定义此前声明的 impl无需重复关联常量赋值 impl as Interface1 where _ { } // extend impl 只允许出现在类作用域的声明上 extend impl as Interface3 where .T3 f32 { } } // 定义此前声明的实现API 或 impl 文件均可 impl MyClass as Interface2 where _ { }匹配matching与一致agreeing的规则同样在文档中给出接口/命名约束的声明在名称解析后同名即匹配一致要求引入关键字相同、参数列表类型与顺序相同参数名可省略若两处都写则必须相同。实现声明的匹配要求类型、接口表达式及forall子句如有均匹配。4. 权衡与备选方案为什么是这些规则4.1 备选一允许声明与定义分离在不同文件被否决曾考虑允许接口或命名约束的声明写在 API 文件、定义写在同库的 impl 文件。相关用例讨论见 #931 提案泛型 impl 访问细节 的 Private interfaces in public API files 小节以及 issue #971。在 2022-10-24 的 #generics-and-templates 讨论中社区决定这些用例可以接受甚至允许私有接口的定义也放在 API 文件中。但为了能够用文件内局部信息去检查不完全接口的非法使用最终重申了 #971 的决定并沿用 #1084 提案的要求定义必须与声明在同一文件。这一约束同时服务于第 3.1 节的需求——正因为组合约束C D的冲突检查被推迟才必须保证编译器在推进到定义处时能够拿到全部信息并报错。4.2 备选二对不完全约束的where子句不做专门限制被否决曾考虑不为不完全约束上的where子句设立专门规则理由是那些真正有问题的情况如访问约束成员的where .X T已经被禁止了。去掉该规则后像C where Vector(.Self) is Hashable这类写法C不完全也会被允许。最终决定在出现明确动机用例之前保持更严格的规则即禁止不完全约束上的where子句。4.3 备选三禁止组合不完全约束C D被否决曾考虑禁止用组合不完全约束因为无法立刻诊断两个约束间的冲突。例如constraint C; constraint D; // 无法判断 C 与 D 是否冲突。 fn FT:! C D; // 下面两个定义是冲突的。 constraint C { extends I where .X i32; } constraint D { extends I where .X bool; }但现有使用前向声明构建边/节点图循环引用的示例恰恰需要这一特性因此决定支持它。这反过来成为定义必须与声明同文件要求的动因冲突错误可以在编译器推进到定义处时被检测出来。冲突只能在二者都完整后被发现的代价则由第 2.2 节允许清单中的注释明确承担。5. 原理落地与验证从设计到实现的证据链5.1 实现证据本提案的规则在 Carbon 检查器toolchain/check中有明确落点不完全状态的建模与诊断DiagnoseIncompleteInterface、DiagnoseIncompleteNamedConstraint见 type_completion.cpp区分正在定义与仅前向声明两种不完全形态不完全 facet 类型定义实现的拦截ImplAsIncompleteFacetTypeDefinition见 impl.cpp前向声明语法的解析与 AST 处理interface、constraint等前向声明由语义检查器中对应 handler 处理参见 toolchain/check 目录下handle_interface.cpp、handle_impl.cpp等文件约束间的匹配与一致规则文档 Matching and agreeing 小节details.md定义了接口/约束/实现三者的匹配判定。5.2 测试证据toolchain/check/testdata/interface/incomplete.carbon对extend require impls A、定义体内的extend require、不完全类型参与转换等场景给出期望诊断输出toolchain/check/testdata/impl/incomplete.carbon验证对不完全接口定义实现impl {} as I {}报错、对不完全接口前向声明实现impl C as I;时名字解析失败的场景toolchain/check/testdata/impl/forward_decls.carbon正向验证前向声明实现的合法路径与关联常量赋值约束。这些测试使用仓库的 file_test 框架见 toolchain/testing 与 testing/file_test通过// CHECK:STDERR注释断言精确的诊断文本与源码位置是上述规则事实即代码的完整证据。6. 小结不完全接口能做什么这一设计问题的答案最终沉淀为一套对称的允许/禁止清单允许在声明与约束组合中使用不完全约束包括组合、require impls、前向声明impl禁止任何需要成员可见性的操作成员访问、where子句、extend、蕴含检查、实现定义体。其底层动机是表达力与实现简单性的平衡——这正是 Carbon 语言目标中 code that is easy to read, understand, and write 的要求。围绕该提案的三次备选方案取舍文件内同文件定义、where限制、C D组合共同塑造了最终规则而 type_completion.cpp 等实现文件与interface/incomplete.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),仅供参考