Carbon 泛型细节(二):Adapter、关联类型与参数化接口的设计与演进
2026/9/10 3:28:04 网站建设 项目流程

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 adaptimpl 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)、以及从其他兼容类型组合复用实现FormattedSongByTitleimpl as ... = ...语法直接复用)。adapter 的完整定义(可添加哪些声明、兼容规则、成员访问、类型间转换)见 classes.md 的 adapters 章节。

Adapter 兼容性:HashMap 的例子

考虑一个带 facet 参数的类型,如哈希表:

interface Hashable { ... } class HashMap(KeyT: Hashable, ValueT: type) { fn Find(self, key: KeyT) -> Optional(ValueT); // ... }

由于KeyTValueT是 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 实现 }

SongPlayableSong不仅数据表示相同,Hashable的实现也相同,因此HashMap(Song, i32)HashMap(PlayableSong, i32)之间可以显式转换;而SongHashedByTitle的哈希实现不同,虽然SongSongHashedByTitle是兼容类型,但对应的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:实现了OrderedSong没有)、实现了Hashable(但不同于Song)、继承了SongPrintable。其规则是:查找SongByArtist是否实现接口I时若未找到,编译器会继续查看Song是否实现I,找到则尽可能复用——只要接口函数签名中引用Self的类型都能相应替换转换成功。

需要注意:class B { extend base: A; }的类扩展中基类不能是 final,但class B { extend adapt A; }A是 final 类时也允许。与普通adapt一致,BA没有隐式转换

当接口间出现名字冲突时,可以去掉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.AZ库为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.T1Z.T2两个库中存在针对不同T的实现,由此带来一系列问题:

  • "只导入你使用的"难以度量:YZ除了import语句外从未被提及,却影响着行为;
  • F(a)的解释取决于导入组合:都不导入时报错,都导入时产生歧义,只导入一个时执行的代码完全不同;
  • 无法强制"每接口一种实现"规则来消除歧义。

本质上:如果允许接口参数被推导,就无法保证导入那些"定义了接口参数所用类型"的库。接口实现是 Carbon 中唯一允许开放扩展(open extension)的语言构造,是解决"表达式问题"的关键,但必须限制哪些库能为类型实现接口,以保证使用时必然能看到实现——这正是本提案没有采用可推导接口参数的根本原因。

只保留关联类型、不要接口参数(Swift 路线)被否决

Swift 只使用关联类型,但这样无法用接口表达运算符重载——例如向量既应能与向量相加、也应能与点相加。因此 Carbon 跟随 Rust:在关联类型之外还提供 trait(接口)参数,并以此定义运算符行为。

与其他语言的横向对比小结

机制CarbonRustSwift
关联类型关联常量 / 关联 facet(let+whereassociated typesassociated types
接口参数参数化接口(checked)generic traits无(只有关联类型)
关联类型推断不支持(需显式或auto不支持支持(曾考虑移除)
运算符重载建模参数化接口如EqWith(T)泛型 trait + 运算符重载运算符重载受限于关联类型

Rust 术语中 interface 参数与关联 facet 都叫 "type parameters",但 Carbon 沿用了 Rust RFC 0195 的区分:接口参数是"输入"(决定选择哪个 impl),关联常量是"输出"(由 impl 决定、不参与选择)。

结论与延伸阅读

本提案确立了 Carbon 泛型细节三块地基:adapter 提供类型视图切换、关联常量让接口签名随实现变化、参数化接口表达一族可多重实现的接口。围绕adapterlet :!常量、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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询