ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Carbon 语言下标表达式(Indexing)设计详解:IndexWith 与 IndirectIndexWith 接口机制

Carbon 语言下标表达式(Indexing)设计详解:IndexWith 与 IndirectIndexWith 接口机制 Carbon 语言下标表达式Indexing设计详解IndexWith 与 IndirectIndexWith 接口机制【免费下载链接】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 语言官方设计文档 docs/design/expressions/indexing.md系统讲解 Carbon 的下标subscript表达式a[i]的设计与实现。文章聚焦 Carbon 如何通过IndexWith与IndirectIndexWith两个核心接口同时支持数组式对值表达式下标产生值与切片式对值表达式下标仍产生可写引用两类语义并深入说明表达式重写规则、blanketfinal impl的连贯性保障以及编译器 checker 中的落地实现。读完本文你将理解如何在 Carbon 中为自己的类型实现下标操作以及下标语法在编译器前端是如何被重写为接口方法调用的。说明Carbon Language 仍处于实验阶段本文所述语法以当前仓库文档与源码为准后续可能演进。概述a[i]的两种语义Carbon 使用与 C 一致的a[i]下标语法。下标的最终结果类别取决于操作数a的表达式类别expression category当a是 durable reference expression持久引用表达式 时下标结果也必然是 durable reference expression即可以直接读写被引用对象当a是 value expression值表达式 时结果可以是 durable reference expression 或 value expression具体由类型实现的接口决定若对值表达式下标产生值表达式如数组该类型应实现IndexWith若对值表达式下标仍产生 durable reference expression如 C 的std::span该类型应实现IndirectIndexWith。这正是数组与切片的差别所在对数组而言只有数组本身是左值l-value时下标才能产生左值而对切片slice这种指向一段数组区域的对象如std::string_view、std::span而言无论切片本身是左值还是右值下标都能产生可写的左值——因为切片内部保存的是指针/引用解引用语义与切片自身的值类别无关。IndirectIndexWith是IndexWith的子类型通过require Self impls IndexWith(SubscriptType)表达继承关系并且它提供了一个blanketfinal impl自动实现IndexWith因此一个类型最多只能实现两者之一从机制上保证了 coherence连贯性任何合法的下标表达式无论编译器掌握多少类型信息都会执行同一份代码。两个核心接口的定义下标语义由以下两个接口定义docs/design/expressions/indexing.mdinterface IndexWith(SubscriptType: type) { let ElementType: type; fn At(bound self, subscript: SubscriptType) - val ElementType; fn Ref(bound ref self, subscript: SubscriptType) - ref ElementType; } interface IndirectIndexWith(SubscriptType: type) { require Self impls IndexWith(SubscriptType); fn Ref(bound self, subscript: SubscriptType) - ref ElementType; }要点说明IndexWith是参数化接口SubscriptType为下标参数类型如i64关联类型ElementType为元素类型IndexWith提供两个方法At以bound self值绑定接收操作数返回val ElementType用于对值表达式下标产生值的场景Ref以bound ref self接收操作数返回ref ElementType用于对引用表达式下标产生引用的场景IndirectIndexWith通过require声明自己是IndexWith的子类型只定义一个Ref以bound self即可因为切片内部有间接层返回ref ElementType文档明确指出这两个接口中用于形成 durable reference expression 的Ref方法必须以ref返回这是形成引用表达式的前提。与 prelude 中实际接口的对应在仓库 core/prelude/operators/index.carbon 中可以看到IndexWith的实际预置实现当前阶段只定义了At是设计文档的阶段性落地package Core library prelude/operators/index; interface IndexWith(SubscriptType: type) { let ElementType: type; fn At(self, subscript: SubscriptType) - ElementType; }该接口通过 core/prelude/operators.carbon 中的export import library prelude/operators/index;随 Core prelude 导出用户代码无需显式导入即可使用下标语法。下标表达式的重写规则一个下标表达式形如_lhs_ [_index_]。其优先级与 C 相同与.、-、函数调用同级且全部左结合。设_lhs_的类型为T_index_的类型为I重写规则如下按顺序判定若T已知实现IndirectIndexWith(I)表达式重写为(_lhs_).(IndirectIndexWith(I).Ref)(_index_)否则若_lhs_是 durable reference expression重写为(_lhs_).(IndexWith(I).Ref)(_index_)否则_lhs_是值表达式重写为(_lhs_).(IndexWith(I).At)(_index_)也就是说只要类型实现了IndirectIndexWith无论操作数类别如何一律走IndirectIndexWith.Ref切片语义否则才根据操作数是否为持久引用在IndexWith.Ref与IndexWith.At之间二选一数组语义。编译器中的落地实现在类型检查器 toolchain/check/handle_index.cpp 中非数组类型的下标通过PerformIndexWith构造一个Operator结构接口名为CoreIdentifier::IndexWith、关联方法为At再交给BuildBinaryOperator完成接口查找与调用重写static auto PerformIndexWith(Context context, Parse::NodeId node_id, SemIR::InstId operand_inst_id, SemIR::InstId index_inst_id) - SemIR::InstId { SemIR::InstId args[] {context.types().GetTypeInstId( context.insts().Get(index_inst_id).type_id())}; Operator op{.interface_name CoreIdentifier::IndexWith, .interface_args_ref args, .op_name CoreIdentifier::At}; return BuildBinaryOperator(context, node_id, op, operand_inst_id, index_inst_id); }对应地toolchain/check/core_identifier.def 中登记了IndexWith这一核心标识符toolchain/check/cpp/operators.cpp 中将其映射为 Clang 的clang::OO_Subscript下标运算符服务于后续 C interop 场景。此外内建数组类型array(T, N)在 handle_index.cpp 中走的是特化路径先把下标隐式转换为i32对值类别为Value的操作数插入ValueAsRef构造临时引用再生成ArrayIndex指令做原语下标对持久引用索引产生持久引用其余情况转回值表达式ConvertToValueExpr。源码中留有 TODO注释为应替换为在IndexWith与IndirectIndexWith之间做选择说明接口化重写仍在演进中。blanketfinal impl为何切片类型无需也不能定义IndexWith如果只有上面的重写规则IndirectIndexWith类型将被迫同时实现三个方法才能支撑下标语法而且这些方法可能被实现得互相矛盾破坏连贯性。为此IndirectIndexWith提供一个 blanketfinal impl自动实现IndexWithfinal impl forall [SubscriptType: type, T: IndirectIndexWith(SubscriptType)] T as IndexWith(SubscriptType) { where ElementType T.(IndirectIndexWith(SubscriptType).ElementType); fn At(bound self, subscript: SubscriptType) - val ElementType { return self.(IndirectIndexWith(SubscriptType).Ref)(index); } fn Ref(bound ref self, subscript: SubscriptType) - ref ElementType { return self.(IndirectIndexWith(SubscriptType).Ref)(index); } }该 blanket impl 的两个方法都只是转发到IndirectIndexWith.Ref从而final关键字保证了任何类型都不能再自己提供IndexWith.At/IndexWith.Ref的实现——一个类型至多实现IndexWith与IndirectIndexWith中的一个杜绝了重复实现与语义分歧类型作者实现IndirectIndexWith时只需要写一个Ref方法工作量最小由于T已知实现IndirectIndexWith(I)时重写规则总是优先选择IndirectIndexWith.Ref这个 blanket impl 实际主要服务于类型信息不完整的泛型上下文例如只知道T: IndexWith而不知道其是否实现IndirectIndexWith时保证行为与完整类型信息下一致。这一设计还隐含一个连贯性推论值类别也要遵守子类别替换规则——l-value 是 r-value 的子类别对任何合法表达式将其中一个 r-value 子表达式替换为类型相同、求值结果相同的 l-value 表达式不得改变外层表达式的合法性与语义。这样一旦确认某个值实现IndexWith再额外获知其实现IndirectIndexWith也不会使既有代码失效或改变语义。实战示例为自定义类型实现下标数组式类型实现IndexWithclass Array(template T: type, template N: i64) { impl as IndexWith(like i64) { let ElementType: type T; fn At(bound self, subscript: i64) - val T; fn Ref(bound ref self, subscript: i64) - ref T; } }数组式类型需要同时提供At与RefAt服务于对数组值表达式的下标返回元素副本Ref服务于对数组持久引用表达式的下标返回元素引用可写。注意这里用like i64作为SubscriptType即i64 类型的值。切片式类型实现IndirectIndexWithclass Span(T: type) { impl as IndirectIndexWith(like i64) { let ElementType: type T; fn Ref(bound ref self, subscript: i64) - ref T; } }切片式类型只需实现一个Ref因为Span内部持有一个指向底层数组的指针无论Span自身是左值还是右值Ref都能返回底层元素的持久引用。这正是std::span的行为。prelude 中的真实案例String仓库中已有真实用例——core/prelude/types/string.carbon 为String实现了IndexWithimpl forall [T: ImplicitAs(i64)] String as IndexWith(T) where .ElementType Char { fn At(self, subscript: T) - Char string.at; }可以看到SubscriptType是泛型T约束为可隐式转换为i64因此整数字面量与各种整数类型都可以作为下标ElementType通过where子句指定为CharAt以内建函数string.at实现。这意味着s[0]这样的表达式最终会重写为对String.At的调用并返回Char值。用测试佐证下标在类型检查器中的行为仓库的 file test 测试直接验证了下标表达式的类型检查与 SEM IR 生成可在 toolchain/check/testdata/index/ 目录找到array_element_access.carbon验证对array(i32, 2)进行常量下标a[0]与变量下标a[b]SEM IR 中均生成array_index指令expr_category.carbon直接对应本文表达式类别主题——对持久引用下标a[0]、a[0] 4;产生ref %i32durable reference对值绑定参数b[0]与函数返回值F()[0]的下标会先构造value_as_ref临时引用再用acquire_value取值验证对数组值下标产生值、对数组引用下标产生引用的规则fail_array_out_of_bound_access.carbon、fail_negative_indexing.carbon、fail_array_large_index.carbon验证越界/负数/超大索引在常量求值阶段的边界检查诊断fail_invalid_base.carbon 与 fail_non_tuple_access.carbon验证对未实现IndexWith的类型如结构体、i64字面量类型下标时报告MissingImplInMemberAccess错误——错误信息中明确出现Core.IndexWith(Core.IntLiteral)印证了 checker 通过IndexWith接口查找实现operators/overloaded/index.carbon 则从用户自定义同名接口角度验证了Core.IndexWith在 prelude 缺失时报CoreNameNotFound以及接口成员At在 SEM IR 中被编译为IndexWith.WithSelf.At关联实体的全过程。运行这些测试的方式见测试文件头部的 TIP 注释例如bazel test //toolchain/testing:file_test \ --test_arg--file_teststoolchain/check/testdata/index/expr_category.carbon备选方案回顾为什么这样设计设计文档与提案 proposals/p002274-subscript-syntax-and-semantics.md 记录了被否决的备选方案理解它们有助于把握当前设计的边界不同的下标语法Different subscripting syntaxes切片本质上是一种间接引用曾考虑为切片使用单独语法如s*[i]并让两个接口互不继承。但独立语法过于新颖、难以拼写且会加剧*的解析负担最终仍统一为a[i]并依赖类型信息选择接口。多下标Multiple indices暂不支持a[i, j]多下标但支持a[(i, j)]单一下标、元组类型。待 variadics可变参数设计完成后接口方法可以变参化以更优雅地支持多维下标。只读下标Read-only subscriptingstring_view、spanconst T这类只读访问类型没有自然的表达路径——数组右值的真不可变与string_view的上下文缺少可变访问权是两回事后者需要类似 Cconst的类型系统与本提案正交留待后续。仅右值下标Rvalue-only subscripting不支持 Pythona[i:j]、Swifta[i...j]这类下标产生切片的操作。若支持需要另一对按值返回的接口如RValueIndexWith/RValueIndirectIndexWith及对应重写规则收益存疑。类 map 下标Map-like subscripting不支持std::map那种下标即插入的语义。理由很实际x m[i];看起来是读操作却可能写入m对读者极具误导性且m[i] x;的隐式插入需要先默认构造再赋值、可能低效。曾考虑对m[i] x这种赋值形式做特殊重写引入Assign方法以及按使用上下文在At/Addr间选择的基于使用的重写但都因增加复杂度或违背类型信息只能从子表达式流向父表达式的原则而未采纳列为潜在未来扩展。相关文档与继续阅读下标接口定义docs/design/expressions/indexing.md表达式类别体系durable reference expression / value expressiondocs/design/values.md完整设计提案与备选方案讨论proposals/p002274-subscript-syntax-and-semantics.md泛型连贯性目标docs/design/generics/goals.md预置接口源码core/prelude/operators/index.carbon、core/prelude/types/string.carbon类型检查器实现toolchain/check/handle_index.cpp、toolchain/check/core_identifier.def相关测试toolchain/check/testdata/index/、toolchain/check/testdata/operators/overloaded/index.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),仅供参考
返回列表