泛型系统
本文档收敛 Dujie 第一阶段的简单泛型系统。
目标不是建立复杂的类型参数体系,而是提供足够支撑通用容器、简单复用和少量约束检查的最小泛型能力。
设计目标
- 只提供最小可用的泛型能力
- 语法简单
- 推导边界清楚
- 不引入复杂子类型、trait 系统或高阶类型
- 不让实现细节反推语言语义
总体原则
泛型系统保持简单
类型位置优先显式
函数调用位置只做局部、可解释的推导
第一阶段只支持极小的约束集合
不做复杂的跨表达式反向推导
支持范围
第一阶段泛型只允许出现在:
- 顶层
func - 顶层
struct
例如:
func id<T>(x: T) -> T {
x
}
struct Box<T> {
value: T,
}
当前不支持:
- 方法级泛型
- 局部函数泛型
- 模块级泛型
- 高阶类型参数
语法
泛型参数列表使用尖括号:
func pair<A, B>(a: A, b: B) -> Pair<A, B> {
Pair { first: a, second: b }
}
struct Pair<A, B> {
first: A,
second: B,
}
规则:
- 多个类型参数使用逗号分隔
- 类型参数作用域仅限当前声明
类型位置的实参
类型位置必须显式写出类型实参。
例如:
let a: Box<string>
let b: Pair<int, string>
第一阶段不支持:
- 类型位置的省略实参
- 部分类型参数推导
泛型函数调用推导
泛型函数调用优先从普通实参做局部推导。
例如:
id(1) // T = int
pair(1, "a") // A = int, B = string
如果调用点无法唯一推导类型参数,则必须显式写出类型实参:
id<int>(1)
当前不做的推导
第一阶段不做这些推导:
- 从赋值目标反推函数泛型实参
- 从返回类型单独反推函数泛型实参
- 跨表达式的复杂联立求解
例如:
let x: Box<string> = make_box()
这里不依赖左侧目标类型去反推 make_box<T>() 的 T。如果普通实参推不出来,就必须显式写:
make_box<string>()
必须显式写类型实参的典型情况
这些值本身不能唯一推出类型参数:
none[]{}- 仅出现在返回类型中的类型参数
例如:
id(none)
make_list()
都不应自动推导成功,除非有显式类型实参。
匿名函数与泛型
第一阶段支持匿名函数,但匿名函数自己不声明泛型参数。
匿名函数可以参与泛型调用,只要其参数类型和返回类型与目标位置要求一致。
例如在内建泛型方法中:
items.map(func(x: int) -> string {
`#${x}`
})
这里匿名函数的参数类型必须与 map 当前实例的元素类型一致,返回类型必须与推导出的目标元素类型一致。
泛型 struct 构造推导
泛型 struct 在构造时允许从字段值推导类型参数,只要能唯一确定。
例如:
struct Box<T> {
value: T,
}
let a = Box { value: "x" } // Box<string>
let b = Box { value: 1 } // Box<int>
如果字段值不能唯一推出类型参数,则必须使用显式类型标注。
例如:
let x: Box<opt<string>> = Box { value: none }
第一阶段先不引入值构造位置的显式类型实参语法,因此不写:
Box<string> { value: "x" }
约束
第一阶段支持约束语法,但只支持极小集合。
语法:
func has_key<K: key, V>(m: map<K, V>, k: K) -> bool {
m.contains(k)
}
规则:
- 约束只允许出现在泛型参数声明上
- 语法形式为
T: constraint - 多个类型参数各自独立声明约束
当前唯一内建约束:key
key 用于表示“可作为 map 键”的类型能力。
当前满足 key 的类型只有:
intboolrunestring
第一阶段不支持:
- 多重约束
where子句- 用户自定义约束
- 基于方法集的复杂约束系统
- 恢复旧的
comparable设计作为通用约束
与参数化容器类型的关系
内建参数化类型:
list<T>map<K, V>opt<T>iter<T>
和用户定义泛型共用同一套类型参数语法,但它们的具体语义仍由各自的专题文档定义,不由本文件重复展开。
这些内建参数化类型不是用户代码定义出来的普通泛型 struct,而是语言内建类型。
因此:
- 语法一致
- 类型参数写法一致
- 但具体操作语义由各自专题定义
例如:
list<T>的push/pop/removeopt<T>的none/some/isiter<T>的map/filter/collect
都属于内建语义,不代表所有用户泛型类型自动拥有类似能力。
语言内建方法可以带泛型语义,例如:
iter<T>.map<U>(...) -> iter<U>iter<T>.filter(...) -> iter<T>iter<T>.collect() -> list<T>
但这不代表当前语言已经开放“用户自定义方法”或“用户自定义方法泛型”。
泛型类型相等性
泛型类型采用名义相等。
规则:
- 同一个泛型声明,类型实参逐个完全相同,类型才相等
- 不同声明即使字段形状相同,也不是同一类型
例如:
Box<string>
Box<int>
Pair<int, string>
其中:
Box<string>与Box<string>是同一类型Box<string>与Box<int>不是同一类型Pair<int, string>与Pair<string, int>不是同一类型
泛型实例兼容性
第一阶段参数化类型统一按不变处理。
也就是说:
Box<string>不是Box<any>list<string>不是list<any>opt<string>不是opt<any>
第一阶段不引入:
- 协变
- 逆变
- 基于子类型关系的参数化兼容
用户泛型类型的能力边界
用户定义的泛型 struct 不会因为“是泛型”而自动获得额外能力。
例如,泛型本身不会自动决定:
- 是否可比较
- 是否可作
mapkey - 是否可迭代
- 是否具有特殊内建方法
这些能力仍由语言内建规则或显式约束单独定义。
当前不讨论的实现问题
本文不规定:
- 单态化还是共享实现
- Rust 代码生成的具体映射方式
- 运行时表示
- trait/object 风格的底层实现策略
这些属于编译器实现问题,不能反向决定语言语义。
当前结论
Dujie 第一阶段的泛型系统可以概括为:
- 只支持顶层
func和顶层struct泛型 - 类型位置显式写实参
- 函数调用位置做局部推导
- 泛型
struct构造可从字段值局部推导 - 约束系统只保留最小的
key