Carbon 泛型细节(二):Adapter、关联类型与参数化接口的设计与演进
【免费下载链接】carbon-langCarbon Language's 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 仓库中的提案 p000731-generics-details-2-adapters-associated-types-parameterized-interfaces.md,系统讲解 Carbon 泛型设计中三个核心机制:适配器(adapter)、关联常量与关联类型(associated constants / associated types)、以及参数化接口(parameterized interfaces)。这是继泛型目标(#24)、泛型术语(#447)、泛型总览(#524)与泛型细节第一部分(#553)之后的系列提案之一,最终内容落地于 docs/design/generics/details.md。读完本文,你将理解这些机制要解决什么问题、为什么采用当前的语法决策、以及它们的实现与编译期查证逻辑在仓库源码中如何体现。
提案背景与定位
Carbon 希望提供高质量泛型能力(目标见 泛型目标提案),但这一特性过于庞大,无法在一次提案中全部落地,因此被拆分为一系列提案逐步细化:
- #24: Generics goals——确立泛型特性目标;
- #447: Generics terminology——统一泛型术语;
- #524: Generics overview——给出泛型特性的高层描述与文档导航;
- #553: Generics details part 1——泛型细节第一部分;
- 本提案(#731)——继续细化adapter、关联类型与其他常量、参数化接口三块内容;
- 后续还有 泛型细节 3(constraints) 等继续推进。
本提案的内容最初提取自一个更大的 Generics combined draft proposal,具体做法是向 docs/design/generics/details.md 这个设计文档新增多个章节。该文档当前已包含完整的 Adapting types、Associated constants、Associated facets 与 Parameterized interfaces 章节,正是本提案结论的延续与落地。
三大核心主题概述
提案将泛型细节的第二批内容划分为三个主题:
| 主题 | 核心问题 | 关键语法机制 |
|---|---|---|
| 适配器(adapters) | 同一类型只能实现接口一次,且实现位置受限,如何为值切换"接口视图" | adapt关键字、extend adapt、impl as ... = ... |
| 关联常量 / 关联类型 | 接口签名中的类型如何随实现变化 | 接口内let常量、where子句赋值 |
| 参数化接口 | 如何表达"一族"相关接口,允许同一类型多次实现 | 接口名后参数列表,如Stack(ElementType: type) |
这三个机制共同服务于一个目标:让checked generics的函数签名能够表达"任意实现了某接口的类型"而不必写出具体类型,同时保持编译期可查证(详见 泛型术语文档)。
关联常量与关联类型:语法决策
使用let声明编译期常量
关联常量(associated constants)指的是接口中除方法之外的其他成员,它们由接口的实现者提供具体值。提案指出,问题 #739: Associated type syntax 的 let 常量部分)保持一致:
interface Stack { let ElementType:! Type; fn Pushaddr me: Self*; ... } class DynamicArray(T:! Type) { ... impl as Stack { let ElementType:! Type = T; fn Pushaddr me: Self*; ... } }这里ElementType就是典型的"关联类型":接口声明它存在,具体类型(如DynamicArray(T))在实现Stack时把它绑定为T。
用auto自动推导类型
如果不想手写约束,可以把类型位置换成auto,由编译器根据=右侧的值自动确定:
class DynamicArray(T:! Type) { ... impl as Stack { let ElementType:! auto = T; fn Pushaddr me: Self*; ... } }这种写法的价值在于接口约束演化时减少改动:当接口的约束被放宽或收紧时,只要=右侧的值仍满足新约束,impl本身无需修改;当约束收紧时,只需修改不满足新约束的实现,再修改接口本身。
备选方案一:省略类型声明
曾考虑在impl中省略类型、始终使用接口中声明的类型:
class DynamicArray(T:! Type) { ... impl as Stack { let ElementType = T; // 无类型标注 fn Pushaddr me: Self*; ... } }该方案在接口约束变化时改动更少,但无法增量地强化约束。最终选择显式把约束写进实现,虽然在某些情况下会产生更多噪音(例如新增约束时即使所有实现已满足也要逐一更新),但好处是获得更多工具来"增量式地推进"接口约束的变更,因此被采纳;提案也明确表示若实践表明这是糟糕的权衡,应当重新评估。
备选方案二:从方法签名推断关联类型(Swift 方案,被拒绝)
Swift 允许在方法签名可推导时省略关联类型的值(见 Swift 官方文档对关联类型的描述)。例如上面的例子只需从上下文推断ElementType == T:
class DynamicArray(T:! Type) { ... impl as Stack { // 不需要写: let ElementType:! Type = T; fn Pushaddr me: Self*; ... } }好处是接口新增关联类型时无需修改所有实现。但提案指出这在存在带默认实现的方法重载时会复杂化,例如:
interface Has2OverloadsWithDefaults { let T:! StackAssociatedType; fn Fme: Self, y: T) { ... } fn Fme: Self { ... } } class S { impl as Has2OverloadsWithDefaults { // 无法确定 T 是 DynamicArray(Int) 还是 // DynamicArray(DynamicArray(Int)). fn Fme: Self), y: DynamicArray(Int)) { ... } } }Swift 曾因关联类型推断是唯一需要全局类型推断的特性而考虑移除(后来决定保留)。Carbon 认为它带来推断复杂度且并非必要,因此仅做了简短讨论便未采纳。
落地现状:where子句与关联常量
需要说明的是,语法在设计演进中有所调整。当前 details.md 中关联常量使用let声明、通过where子句赋值。例如固定维度的点类型:
interface NSpacePoint { let N: i32; // 以下方法要求: 0 <= i < N。 fn Get(ref self, i: i32) -> f64; fn Set(ref self, i: i32, value: f64); // 关联常量可用于签名: fn SetAll(ref self, value: Array(f64, N)); }实现方通过where .N = 2等语法为关联常量赋值:
class Point2D { extend impl as NSpacePoint where .N = 2 { fn Get(ref self, i: i32) -> f64 { ... } fn Set(ref self, i: i32, value: f64) { ... } fn SetAll(ref self, value: Array(f64, 2)) { ... } } }关联常量还有两条硬性约束:不能为final关联常量指定值;没有默认值的关联常量每个实现都必须指定。多个赋值可以用and连接。这些值可作为类型成员直接访问(如Point2D.N == 2),也可在 checked-generic 函数体内使用(如PointT.N作为数组长度)。
关联常量也可以是函数(称为关联函数,associated functions),通过接口内的fn声明,例如反序列化接口:
interface DeserializeFromString { fn Deserialize(serialized: String) -> Self; } class MySerializableType { var i: i32; extend impl as DeserializeFromString { fn Deserialize(serialized: String) -> Self { return {.i = StringToInt(serialized)}; } } } var x: MySerializableType = MySerializableType.Deserialize("3");这里没有使用"用let声明函数类型常量"的写法,而是直接用fn,以与类成员函数的声明语法保持一致(见 classes.md)。
关联 Facet:让方法签名随实现变化
如果关联常量的类型本身是 facet 类型,就得到关联 facet(associated facets)。它们的价值在于可出现在关联方法或函数的签名中,使方法签名随实现而变化。仓库中典型的例子是栈接口:
interface StackAssociatedFacet { let ElementType: type; fn Push(ref self, value: ElementType); fn Pop(ref self) -> ElementType; fn IsEmpty(ref self) -> bool; }DynamicArray(T)实现它时把ElementType绑定到T:
class DynamicArray(T: type) { ... extend impl as StackAssociatedFacet where .ElementType = T { fn Push(ref self, value: ElementType) { self.Insert(self.End(), value); } fn Pop(ref self) -> ElementType { var pos: IteratorType = self.End(); Assert(pos != self.Begin()); --pos; returned var ret: ElementType = *pos; self.Remove(pos); return var; } fn IsEmpty(ref self) -> bool { return self.Begin() == self.End(); } } }有了这个接口,就能写出不依赖具体类型的 checked-generic 函数:
fn PeekAtTopOfStackStackType: StackAssociatedFacet -> StackType.ElementType { var top: StackType.ElementType = s->Pop(); s->Push(top); return top; }从 details.md 的说明看,在 checked-generic 函数内部,StackType.ElementType是一个 archetype(原型类型),其 API 由接口中的声明决定;而在泛型之外,关联 facet 由 impl 查找得到具体值——例如对DynamicArray(i32),StackType.ElementType就是i32。这支撑了 泛型目标文档 中"泛型函数可替代普通函数而不改变调用者所见返回类型"的目标。
关联 facet 还可以用**成员类型(member type)**实现。此外 terminology.md 用"输入/输出"模型给出了清晰的区分:接口参数是"输入",必须先指定才能确定impl;关联常量是"输出",由impl决定、不参与impl选择。例如容器的迭代器类型由容器自身决定,正适合作为关联常量。
参数化接口:一族接口与多重实现
基本形态与"每种参数一种实现"
关联常量不改变"一个类型最多实现一个接口一次"的事实。若想表达一族相关接口(同一类型可为不同参数值提供多个实现),就需要参数化接口,写法是接口名后跟参数列表:
interface StackParameterized(ElementType: type) { fn Push(ref self, value: ElementType); fn Pop(ref self) -> ElementType; fn IsEmpty(ref self) -> bool; }此时StackParameterized(Fruit)与StackParameterized(Veggie)被视为不同的接口、拥有独立的实现。一个类型可以同时实现它们:
class Produce { var fruit: DynamicArray(Fruit); var veggie: DynamicArray(Veggie); extend impl as StackParameterized(Fruit) { fn Push(ref self, value: Fruit) { self.fruit.Push(value); } fn Pop(ref self) -> Fruit { return self.fruit.Pop(); } fn IsEmpty(ref self) -> bool { return self.fruit.IsEmpty(); } } extend impl as StackParameterized(Veggie) { fn Push(ref self, value: Veggie) { self.veggie.Push(value); } fn Pop(ref self) -> Veggie { return self.veggie.Pop(); } fn IsEmpty(ref self) -> bool { return self.veggie.IsEmpty(); } } }接口参数不可推导
与接口中的关联常量、类型参数不同,接口参数不能被推导。改写上面PeekAtTopOfStack的例子会直接产生编译错误:
// ❌ 错误: 无法推导接口参数 `T`。 fn BrokenPeekAtTopOfStackParameterized [T: type, StackType: StackParameterized(T)] (s: StackType*) -> T { ... }原因在于编译器无法确定传入Produce*时T应该是Fruit还是Veggie。解决办法有二:要么把T替换成具体类型:
fn PeekAtTopOfFruitStack [StackType: StackParameterized(Fruit)] (s: StackType*) -> T { ... } var produce: Produce = ...; var top_fruit: Fruit = PeekAtTopOfFruitStack(&produce);要么显式传递T,配合where约束(详见 details.md 中"Another type implements parameterized interface"小节):
fn PeekAtTopOfStackParameterizedImpl (generic T: type, generic StackType: StackParameterized(T), s: StackType*) -> T { ... } fn PeekAtTopOfStackParameterized[StackType: type] (s: StackType*, generic T: type where StackType impls StackParameterized(T)) -> T { return PeekAtTopOfStackParameterizedImpl(T, StackType, s); }运算符重载与多重实现
参数化接口对运算符重载尤其有用:EqWith(T)、OrderedWith(T)这类接口允许一个类型与多个其他类型比较。例如:
interface EqWith(T: type) { fn Equal(self, rhs: T) -> bool; ... } class Complex { var real: f64; var imag: f64; // 只要参数不同,可以多次实现同一接口 extend impl as EqWith(f64) { ... } // 等价于: impl as EqWith(Complex) { ... } extend impl as EqWith(Self) { ... } }接口参数默认都是 checked 参数,因为它们在编译期就必须解析,且允许传入 symbolic 或 template 值。接口参数也不要求一定是 facet 类型,只是绝大多数情况如此——例如把"元组成员读取"操作建模为以index为参数的接口:
interface ReadTupleMember(index: u32) { let T: type; // 返回 self[index] fn Get(self) -> T; }同一参数值不可实现两次:Map 与 Bijection 的教训
当同一类型对相同参数组合实现了两次同一接口时,会产生编译错误:
interface Map(FromType: type, ToType: type) { fn Map(ref self, needle: FromType) -> Optional(ToType); } class Bijection(FromType: type, ToType: type) { extend impl as Map(FromType, ToType) { ... } extend impl as Map(ToType, FromType) { ... } } // ❌ 错误: Bijection 对接口 Map(String, String) 有两个不同的 impl 定义 var oops: Bijection(String, String) = ...;当FromType == ToType时两个 impl 冲突。文档给出的修复方案正是使用适配器容纳反向查找的 impl:
class Bijection(FromType: type, ToType: type) { extend impl as Map(FromType, ToType) { ... } } class ReverseLookup(FromType: type, ToType: type) { adapt Bijection(FromType, ToType); extend impl as Map(ToType, FromType) { ... } }参数化命名约束
不仅接口可以参数化,命名约束(named constraints)也支持参数,其语义与接口参数一致(详见 details.md 的 "Parameterized named constraints" 小节)。
Adapter(适配器):为类型切换接口视图
为什么需要 adapter
由于接口对同一类型最多实现一次,且实现位置受到限制(即孤儿规则的约束,见 details.md),用户需要一种切换值的类型以访问不同接口实现的手段。Carbon 因此提供 adapter:创建与既有类型兼容、但 API(尤其接口实现集合)不同的新类型。仓库的典型示例:
interface Printable { fn Print(self); } interface Ordered { fn Less(self, rhs: Self) -> bool; } class Song { extend impl as Printable { fn Print(self) { ... } } } class SongByTitle { adapt Song; extend impl as Ordered { fn Less(self, rhs: Self) -> bool { ... } } } class FormattedSong { adapt Song; extend impl as Printable { fn Print(self) { ... } } } class FormattedSongByTitle { adapt Song; extend impl as Printable = FormattedSong; extend impl as Ordered = SongByTitle; }可以看到 adapter 支持三种典型用法:为原类型补充新接口实现(SongByTitle)、提供同一接口的不同实现(FormattedSong)、以及从其他兼容类型组合复用实现(FormattedSongByTitle用impl as ... = ...语法直接复用)。adapter 的完整定义(可添加哪些声明、兼容规则、成员访问、类型间转换)见 classes.md 的 adapters 章节。
Adapter 兼容性:HashMap 的例子
考虑一个带 facet 参数的类型,如哈希表:
interface Hashable { ... } class HashMap(KeyT: Hashable, ValueT: type) { fn Find(self, key: KeyT) -> Optional(ValueT); // ... }由于KeyT、ValueT是 checked 参数,Find只能使用参数类型被声明要求的那些能力。基于这一点可以判定:两个 adapter 之间何时允许转换。设有两个Song的 adapter:
class PlayableSong { adapt Song; extend impl as Hashable = Song; // 复用 Song 的 Hashable 实现 extend impl as Media { ... } } class SongHashedByTitle { adapt Song; extend impl as Hashable { ... } // 不同的 Hashable 实现 }Song与PlayableSong不仅数据表示相同,Hashable的实现也相同,因此HashMap(Song, i32)与HashMap(PlayableSong, i32)之间可以显式转换;而SongHashedByTitle的哈希实现不同,虽然Song与SongHashedByTitle是兼容类型,但对应的HashMap类型不兼容——因为 HashMap 的不变量依赖哈希函数保持不变。
扩展 adapter:extend adapt
多数情况下 adapter 希望保留原类型的大部分 API,最常见的是"新增"或"替换"某个接口实现。用extend前缀修饰adapt即可从原类型既有 API 出发(extend同时扩展成员访问与 impl 查找,见 member_access.md):
class SongByArtist { extend adapt Song; // 新增一个接口实现 extend impl as Ordered { ... } // 用另一种实现替换既有实现 extend impl as Hashable { ... } }结果SongByArtist:实现了Ordered(Song没有)、实现了Hashable(但不同于Song)、继承了Song的Printable。其规则是:查找SongByArtist是否实现接口I时若未找到,编译器会继续查看Song是否实现I,找到则尽可能复用——只要接口函数签名中引用Self的类型都能相应替换转换成功。
需要注意:class B { extend base: A; }的类扩展中基类不能是 final,但class B { extend adapt A; }在A是 final 类时也允许。与普通adapt一致,B到A没有隐式转换。
当接口间出现名字冲突时,可以去掉extend实现接口,再用alias单独引入或重命名所需名字:
class SongRenderToPrintDriver { extend adapt Song; // 新增一个 Print() 成员函数 fn Print(self) { ... } // 与新的 Print 避免名字冲突: // 以非 extend 方式实现 Printable impl as Printable = Song; // 把 Printable.Print 以 PrintToScreen 名字暴露 alias PrintToScreen = Printable.Print; }实战用例一:组合独立开发的库
两个包CompareLib(定义CompareLib.Comparable接口与 checked-generic 算法CompareLib.Sort)与SongLib(定义类型SongLib.Song)彼此无依赖,因此任何一方都不会为对方定义实现。用户可定义一个 adapter 为SongLib.Song提供CompareLib.Comparable实现:
import CompareLib; import SongLib; class Song { extend adapt SongLib.Song; extend impl as CompareLib.Comparable { ... } } // 或者不把 CompareLib.Comparable 的名字混入 Song 的 API: class Song { extend adapt SongLib.Song; } impl Song as CompareLib.Comparable { ... }调用时既可以把SongLib.Song显式转换为Song,也可以直接使用Song值:
var lib_song: SongLib.Song = ...; CompareLib.Sort((lib_song as Song,)); var song: Song = ...; CompareLib.Sort((song,));实战用例二:为其他类型提供可复用实现
可以定义一个以被适配类型为参数的 adapter,实现某个接口,再通过impl as ... = ...语法把它"拉"进来复用。例如为所有实现了Difference接口的类型提供Comparable:
interface Comparable { fn Less(self, rhs: Self) -> bool; } interface Difference { fn Sub(self, rhs: Self) -> i32; } class ComparableFromDifference(T: Difference) { adapt T; extend impl as Comparable { fn Less(self, rhs: Self) -> bool { return (self as T).Sub(rhs) < 0; } } } class IntWrapper { var x: i32; impl as Difference { fn Sub(self, rhs: Self) -> i32 { return left.x - right.x; } } impl as Comparable = ComparableFromDifference(IntWrapper); }实战用例三:私有实现(Private impl)
当库公开某个类型、但只想把"该类型实现了某接口"作为内部实现细节时,可为该类型创建私有 adapter 并在其上实现接口,成员方法通过把self转换到 adapter 类型来使用该私有实现:
// 公开,位于 API 文件 class Complex64 { // ... fn CloserToOrigin(self, them: Self) -> bool; } // 私有 class ByReal { extend adapt Complex64; // 复数通常不可比较,但这个比较函数对某些方法实现很有用。 extend impl as Comparable { fn Less(self, that: Self) -> bool { return self.Real() < that.Real(); } } } fn Complex64.CloserToOrigin(self, them: Self) -> bool { var self_mag: ByReal = self * self.Conj() as ByReal; var them_mag: ByReal = them * them.Conj() as ByReal; return self_mag.Less(them_mag); }实战用例四:便捷访问接口名字
如果函数要调用某接口的多个函数,而类型并未extend该接口的实现,每次都要使用限定成员访问会比较啰嗦。adapter 可以把"实现了该接口"变成类型本身 API 的一部分:
interface DrawingContext { fn SetPen(self, ...); fn SetFill(self, ...); fn DrawRectangle(self, ...); fn DrawLine(self, ...); ... } impl Window as DrawingContext { ... } class DrawInWindow { adapt Window; extend impl as DrawingContext = Window; } fn Render(w: Window) { let d: DrawInWindow = w as DrawInWindow; d.SetPen(...); d.SetFill(...); d.DrawRectangle(...); ... }细节文档还提示:也可以通过局部 symbolic facet 常量达到同样效果(let generic DrawInWindow: Draw = Window;),这属于另一条路径。
源码层面的印证
adapter 并非纸上设计,在工具链实现中已有明确落点:
- toolchain/check/class.cpp 负责校验 adapter 定义的合法性,定义了
AdaptWithBase(adapter 带基类)、AdaptWithFields(adapter 带字段)、AdaptWithVirtual(adapter 带虚函数)等诊断错误;同时规定 adapter 的对象表示(object representation)就是被适配类型的对象表示; - toolchain/check/convert.cpp 在类型转换逻辑中处理 base 与 adapt 关系,包括 tuple/struct 的逐部分转换以及沿 adapter 链走到被适配类型的转换路径。
这印证了"adapter 是对象表示相同、接口视图不同"的语义,且相关规则已被编译器实现与诊断覆盖。
被否决的备选方案与理由
为什么是adapter而不是adaptor
两种拼写都有依据,但-er拼写在英文文本和代码中更常见,且 GoF《设计模式》一书采用-er拼写(adapter pattern),因此最终选定adapter。
值模式(Value patterns)被否决
曾考虑允许函数参数使用不带:的值模式,以便把T绑定到参数列表中较后出现的类型:
fn PeekAtTopOfStackParameterized [T:! Type, StackType:! StackParameterized(T)] (s: StackType*, T) -> T { ... }但 Carbon 不希望普遍开放值模式——否则fn F(Int)这类声明会被接受,而用户几乎总是想写fn F(i: Int)。为保留对这类笔误的报错能力,该方案被否决。
"可推导接口参数"被否决及其与一致性(coherence)的关系
曾考虑区分两种接口参数:"multi" 参数(即现在的参数化接口参数)与"deducible"(可推导)类型参数。后者只允许一个类型对接口有一种实现,可像关联类型一样被推断:
fn PeekAtTopOfStack[ElementType:! Type, StackType:! Stack(ElementType)] (s: StackType*) -> ElementType { ... }提案给出了系统的否决理由:
- 只有一种参数使语言更简单;
- multi 参数表达了确实需要的东西,而可推导参数总能改写为关联类型;
- "每个接口 × 参数组合一种实现"与其他参数化构造(如
Foo(A)与Foo(B)是两个不同且无关的类型)更一致; - 难以给出"何时用关联类型、何时用可推导参数"的清晰指引;
- 结构接口中的可推导参数需要额外规则确保无歧义推导。
最关键的是,可推导接口参数会复杂化 impl 的查找规则,并可能破坏一致性(coherence,见 docs/design/generics/goals.md)。提案用一组包/库的例子说明问题:假设X库定义了接口I(T)与类型A,而Y库为X.I(Y.T1)实现X.A、Z库为X.I(Z.T2)实现X.A:
package X library "I and A" api; interface I(Type:$ T) { ... } struct A { ... }package Y library "T1" api; import X library "I and A"; struct T1 { ... } // 类型 X.A 对 X.I(T) 有实现,其中 T == Y.T1。 impl X.I(T1) for X.A { ... }package Z library "T2" api; import X library "I and A"; struct T2 { ... } // 类型 X.A 对 X.I(T) 有实现,其中 T == Z.T2。 impl X.I(T2) for X.A { ... }package Main api; import X library "I and A"; // 考虑如果组合包含下面两句的不同组合会怎样: // import Y library "T1"; // import Z library "T2"; // 函数 F 用值 a(类型 U)调用,其中 U 对某个 T 实现了接口 X.I(T)。 fn FType:$ T, X.I(T):$ U { ... } fn Main() { var X.A: a = X.A.Init(); F(a); }调用F(a)会触发对接口X.I(T)的查找,而Y.T1与Z.T2两个库中存在针对不同T的实现,由此带来一系列问题:
- "只导入你使用的"难以度量:
Y、Z除了import语句外从未被提及,却影响着行为; F(a)的解释取决于导入组合:都不导入时报错,都导入时产生歧义,只导入一个时执行的代码完全不同;- 无法强制"每接口一种实现"规则来消除歧义。
本质上:如果允许接口参数被推导,就无法保证导入那些"定义了接口参数所用类型"的库。接口实现是 Carbon 中唯一允许开放扩展(open extension)的语言构造,是解决"表达式问题"的关键,但必须限制哪些库能为类型实现接口,以保证使用时必然能看到实现——这正是本提案没有采用可推导接口参数的根本原因。
只保留关联类型、不要接口参数(Swift 路线)被否决
Swift 只使用关联类型,但这样无法用接口表达运算符重载——例如向量既应能与向量相加、也应能与点相加。因此 Carbon 跟随 Rust:在关联类型之外还提供 trait(接口)参数,并以此定义运算符行为。
与其他语言的横向对比小结
| 机制 | Carbon | Rust | Swift |
|---|---|---|---|
| 关联类型 | 关联常量 / 关联 facet(let+where) | associated types | associated types |
| 接口参数 | 参数化接口(checked) | generic traits | 无(只有关联类型) |
| 关联类型推断 | 不支持(需显式或auto) | 不支持 | 支持(曾考虑移除) |
| 运算符重载建模 | 参数化接口如EqWith(T) | 泛型 trait + 运算符重载 | 运算符重载受限于关联类型 |
Rust 术语中 interface 参数与关联 facet 都叫 "type parameters",但 Carbon 沿用了 Rust RFC 0195 的区分:接口参数是"输入"(决定选择哪个 impl),关联常量是"输出"(由 impl 决定、不参与选择)。
结论与延伸阅读
本提案确立了 Carbon 泛型细节三块地基:adapter 提供类型视图切换、关联常量让接口签名随实现变化、参数化接口表达一族可多重实现的接口。围绕adapter、let :!常量、where赋值等语法选择,提案记录了完整的设计权衡过程(尤其是对 Swift 关联类型推断与"可推导接口参数"的否决,以及对一致性/coherence 的守护)。当前设计文档中的语法在细节上有进一步演进(如关联常量通过where子句赋值),但核心概念与取舍一脉相承,且 adapter 的合法性检查已在 toolchain/check/class.cpp 等编译器源码中落地。
继续深入可参考以下仓库文档:
- 设计细节全文:docs/design/generics/details.md(含 adapting types、associated constants、associated facets、parameterized interfaces 各节)
- 高层总览:docs/design/generics/overview.md
- 术语澄清:docs/design/generics/terminology.md("Interface parameters and associated constants"一节)
- 泛型目标:docs/design/generics/goals.md
- 类与 adapter 的完整定义:docs/design/classes.md
- 系列后续:泛型细节 3(constraints)
【免费下载链接】carbon-langCarbon Language's 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),仅供参考