Carbon 语言名义类与方法设计:解读 p000722 提案及其在工具链中的落地
Carbon 语言名义类与方法设计解读 p000722 提案及其在工具链中的落地【免费下载链接】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/p000722-nominal-classes-and-methods.md 展开剖析 Carbon 如何为名义nominal记录类型引入类class与方法method机制包括方法语法接收者参数me/self、按地址传递关键字addr/ref、名义数据类impl as Data、函数与方法的差异边界以及let常量与编译期值引入规则。读者将理解这些设计决策背后的取舍过程并能在 docs/design/classes.md 与 toolchain/check 源码中找到对应落地点。提案定位从基本类到名义类与方法要解决的问题Carbon 需要一个机制来定义新的名义记录类型nominal record types并让它们支持面向对象特性尤其是方法methods。这正是提案 p000722 的 Problem 章节提出的核心问题。背景这是 #561 的延续提案在 Background 中明确指出它是 proposals/p000561-basic-classes-use-cases-struct-literals-struct-types-and-future-work.md#561的后续。后者奠定了类的基础引入了**结构化数据类struct types**与struct 字面量确立了所有记录类型都是类的一种的术语体系{.a 2}是 struct 字面量{.a: i64}是 struct 类型此外还有名义数据类。#561 解决了类型怎么定义的方向性问题而 #722 则要回答类的成员尤其是方法怎么写这一具体问题。提案内容与目标提案正文非常精简更新 docs/design/classes.md 中的类设计。其 Rationale 聚焦于 Carbon 的项目目标之一代码易于阅读、理解与编写而性能等其他方面留待未来提案处理。换言之p000722 的价值不在正文而在其详尽的 Alternatives considered备选方案章节——它记录了一次重要的语言设计决策全过程。方法语法一次完整的语言设计决策方法语法由 question-for-leads issue #494: Method syntax 拍板提案记录了大量被否定的备选方案。这些方案使用当时已确定的参数语法issue #542书写method (this: Self*) Set(n: Int); fn [Me*].Set(n: Int); fn -Set(n: Int); fn Set(n: Int); fn Set(s: Self*, n: Int); fn Set(this self: Self*, n: Int); fn Setthis self: Self*; fn Set(self: Self*; n: Int); fn Set(self: Self*)(n: Int);这些方案覆盖了以下维度不同的引入符如用method而非fn来区分方法与函数接收者模式的位置放在方法名之前、放在额外的括号/尖括号/方括号中接收者名字与类型是否省略用关键字标记接收者类似 C 的 Deducingthis 提案P0847R6参数列表内的分隔符如用;分隔接收者与其余参数但被认为过于隐晦。提案逐项给出了采纳与否决的理由下面是最关键的几条决策主线。完整接收者类型类型信息单方向流动设计者希望允许完整指定接收者receiver的类型这与 C 用const关键字放在方法声明末尾来控制this类型的做法形成对比——后者把类型信息切碎在特殊语法里。采纳完整类型后还可以基于类型参数条件性地启用方法。提案给出的例子class FixedArray(T:! Type, N:! Int) { // ... fn Print[me: FixedArray(P:! Printable, N)]() { ... } }即当T满足Printable接口时FixedArray(T, N)才拥有Print()方法。接收者类型可选省略类型的方案曾被考虑过但未作为首选留待未来。方法名对齐信息密度优先方法与函数共用同一个fn引入符由 issue #463 决定且方法名紧随其后使得函数名与方法名在视觉上对齐在同一列struct IntContainer { // Non-methods for building instances fn MakeFromInts ... - IntContainer; fn MakeRepeating ... - IntContainer; // Methods fn Size ... - Int; fn First ... - Int; fn Clear ...; fn Append ...; }这样便于快速扫描并把最重要的信息名字放在最前面。设计者也考虑过用另一个 2 字符引入符如me来区分方法与关联函数但me对方法的暗示不够强且人们更倾向与其他函数声明保持一致还考虑过用roread-only与rwread-write区分接收者按值/按地址传递同样未获支持。接收者放在方括号与推导参数同区把接收者模式放进显式参数列表Python 风格会造成调用处实参列表与声明处参数列表的不匹配因此提案把接收者模式放入方括号[...]与推导参数放在一起。虽然也有顾虑——接收者并不像推导参数那样真正推导——但该方案最终胜出。接收者参数固定命名为me接收者名字固定有几个好处一致性利于读者也简化了在函数间复制、移动代码可识别性编译器可以在声明中直接辨认接收者模式从而区分方法与普通关联函数类似 C 的静态方法短名方法体内通过显式成员访问访问对象成员Carbon 方法内没有隐式成员访问所以需要一个足够短的名字——me因此入选。addr关键字指示按地址传递这里有一个精妙的类型系统决策。设计者考虑过用接收者类型是否为指针类型来隐式表示按地址传递但希望类型信息只朝一个方向流动——否则在推导接收者对象类型时推导结果无法反过来决定指针还是非指针。引用类型能绑定左值而无需显式取地址也被否定了因为会给类型系统增加大量复杂度且设计者认为引用在其他场景并非必需。最终方案是参数声明中用一个与类型分离的标记来匹配实参的值类别value category。该标记意味着先取实参的地址再匹配模式其余部分并且只有会修改对象的可变方法才需要它——这要求接收者必须是左值。候选写法有三种fn Setme: Self*; fn Set*(me: Self*); fn Setref me: Self*;最终敲定关键字addrfn Setaddr me: Self*;理由很务实不占用符号、更利于搜索引擎检索、未来可扩展更多调用约定关键字如inout甚至可能在inout语义更优时放弃addr。这一关键字标记做法也与模板参数用template关键字标记的决策issue #565 on generic syntax保持了一致。函数与方法的差异边界issue #494 还系统性地梳理了方法相对函数的六点差异调用语法不同方法用x.F(n)接收者传递方式不改变调用语法方法声明区分按值或按指针接收接收者x但调用处写法不变声明位置受限方法必须在类体内声明私有成员访问方法与关联函数都能访问类的私有成员动态分发只有方法可以选择使用动态分发virtual dispatch协变方法的接收者参数在继承中呈协变变化这与普通参数类型不同。这六点定义了方法在 Carbon 中相对普通函数的最小特权集也为后续 proposals/p000777-inheritance.md继承与虚方法等提案划定了边界。被否决的备选方案在调用点标记可变方法一个备选思路是让按地址传递接收者在调用点可见用取地址运算符(x).Set(4);这实际上会让可变方法变成指针类型上的方法而非类类型上的方法最终被否决。把链接性并入访问修饰符设计者考虑过internal、private.internal这类既限制可见性又强制内部链接的访问修饰符但决定推迟在缺乏实践检验的情况下不提前引入复杂度先用纯实现技术解决等链接性成为可见问题再加。名义数据类的命名Data而非DataClassKotlin 用data关键字把类声明为数据类Carbon 则选择复用已有的类型实现接口机制——在类体内写impl as Data {}从而支持为所有数据类做 blanket 实现这类统一表达。名称特意选Data而非DataClass因为元组tuples也隐式实现该接口而元组是积类型product types而非类。这一定位在 docs/design/classes.md 的 Nominal data classes 一节被完整继承class TextLabel { var x: i32; var y: i32; var text: String; // This line makes TextLabel a data class, which defines // a number of operations field-wise. impl as Data {} }let常量以:!明确区分编译期值C 中const int变量会尝试常量求值然后按求值是否成功赋予不同语义这种隐式行为被 Carbon 设计者视为反面教材。因此提案提出所有需要在编译期使用的值都必须用:!显式标记类成员与函数体内的let声明保持一致的解释。从提案到当前实现源码中的落地提案落地后的设计在 docs/design/classes.md 中持续演进语法细节也随之调整me演变为selfaddr演变为ref但提案确立的决策骨架仍清晰可辨。方法即带self参数的成员函数在 docs/design/classes.md 的 Member functions 一节中方法被定义为首个显式参数为self的成员函数类型缺省为Self方法体内通过self显式访问成员ref self表示实参必须是引用表达式用于会修改对象的可变方法class Circle { fn Diameter(self) - f32 { return self.radius * 2; } fn Expand(ref self, distance: f32); var center: Point; var radius: f32; }方法仍可等价地通过类型名直接调用Circle.Diameter(c)这与提案中方法是带显式接收者参数的函数的建模一脉相承。若存在推导的编译期参数它们照常放在方括号[...]中self仍是圆括号中的第一个参数。类检查的编译器实现在工具链中类的语义检查集中在 toolchain/check/class.cpp 与 toolchain/check/handle_class.cppSetClassSelfTypeclass.cpp 第 30-34 行在类定义开始时把Self类型绑定到当前类的类型StartClassDefinitionclass.cpp 第 36-52 行创建类的命名作用域并通过AddRequiredName将Self作为必需名字引入作用域——这正是提案与设计文档中Self指代当前类型的编译器侧实现handle_class.cpp 负责校验extend base必须出现在字段声明之前、处理字段类型与base成员的绑定以及适配器adapter类对字段/虚函数的限制adapter with base class、adapter with fields、adapter with virtual function 等诊断。此外名义数据类所依赖的Data接口精神体现在 core/prelude 中大量以impl as ...方式声明的接口实现如impl as Copy、impl as Iterate它们正是类型通过实现接口获得字段级操作这一机制的实际运用。结语p000722 提案的价值在于它为 Carbon 类的两大支柱——名义类型与方法——确立了设计方向方法复用fn引入符、以固定命名的接收者参数区分方法与关联函数、以addr后演化为ref关键字显式标记按地址传递、以impl as Data复用接口机制表达数据类语义。这些决策今天仍可在 docs/design/classes.md 与 toolchain/check 中追溯。对语言设计感兴趣的读者可以沿 proposals/p000561-basic-classes-use-cases-struct-literals-struct-types-and-future-work.md → p000722 → proposals/p000777-inheritance.md 的提案链条观察一个类型系统能力是如何一步步生长出来的。【免费下载链接】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),仅供参考