Appearance
泛型
本文档记录泛型设计,覆盖泛型函数、函数级整数 const 参数、泛型 struct、泛型 variant、泛型 concept、泛型固有 impl、泛型 concept impl、默认泛型参数、参数包、template for 展开以及 requires 约束。
函数泛型采用偏现代 C++ 的强模板模型:无约束泛型也可以在函数体内使用依赖于类型参数的操作;concept 和 requires 是可选约束机制,用于提前表达能力要求、改善诊断、约束公共 API 和辅助重载选择。
目标
函数泛型同时满足两类写法:
cp
add<T>(left: T, right: T) -> T
{
return left + right;
}cp
add_by<T: addable>(left: T, right: T) -> T
{
return left.add(right);
}第一种写法保留模板的表达力。left + right 依赖类型参数 T,在泛型函数定义阶段不要求立即证明所有 T 都支持加法;当某处实际使用 add<i32>、add<f64> 或 add<some_type> 时,编译器再用具体类型检查实例化后的函数体。
第二种写法显式声明能力要求。它不负责恢复模板能力,而是让调用者和编译器更早知道该泛型需要 T 满足 addable。
语法总览
text
GenericFunction
-> identifier GenericParameterList? ( ParameterList? ) ReturnType? RequiresClause? Block
GenericStruct
-> export? struct identifier GenericParameterList? { FieldDecl* }
GenericVariant
-> export? variant identifier GenericParameterList? { VariantCase* }
GenericConcept
-> export? concept identifier GenericParameterList? { ConceptItem* }
InherentImpl
-> impl GenericParameterList? TypePattern RequiresClause? { ImplItem* }
ConceptImpl
-> impl GenericParameterList? ConceptId for TypePattern RequiresClause? { ConceptImplItem* }
GenericParameterList
-> < GenericParameter ( , GenericParameter )* >
GenericParameter
-> identifier GenericDefault?
| identifier : IntegerGenericKind GenericDefault?
| identifier : ConceptBoundList GenericDefault?
| identifier ...
| identifier ... : ConceptBoundList
IntegerGenericKind
-> usize
| isize
GenericDefault
-> = TypeArgument
ConceptId
-> identifier TypeArgumentList?
ConceptBoundList
-> ConceptId ( and ConceptId )*
RequiresClause
-> requires RequiresConstraintExpr
RequiresConstraintExpr
-> RequiresAnd
RequiresAnd
-> RequiresPrimary ( and RequiresPrimary )*
RequiresPrimary
-> RequiresConstraint
| ( RequiresConstraintExpr )
RequiresConstraint
-> Type : ConceptBoundList
| TypePack : ConceptBoundList
| Type == Type
| identifier
TypePattern
-> Type
TypePack
-> identifier ...
TemplateFor
-> template for ( TemplateForBinding : PackExpansion ) Block
TemplateForBinding
-> let identifier
| const identifier
| type identifier
PackExpansion
-> identifier ...函数名之后、参数列表之前可以出现泛型参数列表:
cp
id<T>(value: T) -> T
{
return value;
}泛型参数默认是编译期类型参数。函数泛型还支持第一版整数 const 参数,写作 N: usize 或 I: isize,用于 [T; N] 这样的类型模式。
泛型参数也可以声明为类型参数包:
cp
print<T...>(values: T...)
{
template for(let value : values...) {
write(value);
}
}T... 表示零个或多个类型参数,values: T... 表示与 T... 等长的值参数包。参数包规则见“参数包与 template for”。
泛型参数可以带默认实参:
cp
struct pair<T, U = T> {
first: T;
second: U;
}
concept partial_eq<Rhs = this> {
equals(self const&, rhs: Rhs const&) -> bool;
}默认实参按声明顺序求值,可以引用前面已经声明的泛型参数。调用或使用类型构造器时,显式实参按前缀匹配,缺省位置由默认实参补齐:
cp
let same: pair<i32> = pair<i32>{ .first = 1, .second = 2 };
let mixed: pair<i32, bool> = pair<i32, bool>{ .first = 1, .second = true };函数参数也可以带默认值。默认值只能用于尾部参数,调用时只能省略尾部实参:
cp
sort<T: mutable_object, Compare: strict_weak_order<T> = less<T>>(values: span<T>, compare: Compare = Compare{}) -> void
{
// ...
}
sort(values); // compare 使用 Compare{}
sort(values, greater<i32>{}); // 显式降序比较器函数默认表达式在泛型实参确定后检查,因此 Compare{} 会在 Compare 被推导或由默认泛型实参补齐后再按参数类型检查。默认表达式可以引用前面已经声明的泛型参数和值参数。
this 可以出现在 concept 泛型参数默认值中,表示当前被检查或被实现的类型。因此:
cp
T: partial_eq
impl partial_eq for i32
impl partial_eq for box<T>分别按上下文展开为:
text
T: partial_eq<T>
impl partial_eq<i32> for i32
impl partial_eq<box<T>> for box<T>标准库和编译器可以提供内建 concept。mutable_object 由类型系统判断是否为可写对象类型;equality_comparable<Rhs = this> 按 == 是否可用判断;incrementable 按前置 ++ 是否可用判断。它们用于 sort、swap、iota 这类需要提前表达类型能力的泛型算法和范围。
无约束泛型
无约束类型参数不声明任何 concept:
cp
id<T>(value: T) -> T
{
return value;
}
make_pair<T, U>(first: T, second: U) -> (T, U)
{
return (first, second);
}无约束泛型允许使用依赖于类型参数的操作:
cp
max<T>(left: T, right: T) -> T
{
if(left < right) {
return right;
}
return left;
}
len<T>(value: T) -> i32
{
return value.size();
}这些操作不是对所有 T 都合法,而是形成依赖语义:
left < right依赖T。value.size()依赖T。T::item这类关联类型查找依赖T。
编译器在泛型函数定义阶段只检查语法、泛型参数绑定、非依赖名字和明显不依赖 T 的错误。实际类型实参确定后,编译器把 T 替换为具体类型,并对实例化后的函数体执行完整语义检查。
例如:
cp
let a = max(1, 2); // 实例化 max<i32>
let b = max(1.0, 2.0); // 实例化 max<f64>如果某个类型不支持 <,则对应的 max<that_type> 实例化失败。
UFCS 与依赖调用
UFCS 规则见 struct.md。泛型函数中,接收者或首个实参依赖类型参数时,UFCS 查找可以延迟到实例化阶段:
cp
call_size<T>(value: T) -> i32
{
return value.size();
}
call_free<T>(value: T) -> i32
{
return size(value);
}在泛型函数定义阶段,value.size() 和 size(value) 都可以形成依赖调用。实例化时替换 T 后,再按普通 UFCS 规则解析:
value.size()先查具体类型的成员size,名字不存在时再查自由函数size(value)。size(value)先查当前可见自由函数size;如果当前在成员函数体内,名字不存在时先查self.size(value);仍不存在且调用至少有一个参数时,再查具体类型的成员value.size()。
回退只在首选路径中不存在同名函数时触发。若同名函数存在,但实例化后的参数类型不匹配、self 可变性不满足、返回类型不符合上下文,或 concept / requires 约束不满足,都应报告该路径上的错误,不继续尝试另一种 UFCS 形式。
如果调用不依赖类型参数,则在泛型函数定义阶段立即按同一规则解析。非依赖名字已经存在但签名不匹配时,不能把它延迟到实例化阶段。
内联 concept 约束
简单约束可以写在泛型参数中:
cp
max<T: comparable>(left: T, right: T) -> T
{
if(left.less(right)) {
return right;
}
return left;
}多个 concept 使用 and:
cp
sort_value<T: comparable and movable>(value: T) -> T
{
return value;
}T: comparable and movable 的语义是:
text
T implements comparable
T implements movable这里采用 T: concept 作为能力约束写法。约束中的 concept 可以带泛型实参;如果省略实参,则按 concept 声明中的默认泛型参数补齐。
内联约束适合短约束。它等价于把同样的 concept 要求写进 requires:
cp
max<T>(left: T, right: T) -> T
requires
T: comparable
{
if(left.less(right)) {
return right;
}
return left;
}requires 子句
复杂约束写在函数头后的 requires 子句中:
cp
copy_all<I: iterator, O: sink>(input: I, output: O)
requires
I::iter_item == O::sink_item
{
}requires 约束表达式可以包含:
- 类型参数的 concept 要求。
- 多个 concept 的
and组合。 - 类型相等约束,常用于关联类型。
- 在
concept体内,裸 concept 名表示this必须满足该父 concept。
例如:
cp
merge<T, I, O>(input: I, output: O)
requires
T: comparable
and I: iterator
and O: sink
and I::iter_item == T
and O::sink_item == T
{
}推荐规则:
- 简单的一两个 concept 约束写在
<T: concept>中。 - 类型相等、多个类型参数之间的关系、较长约束写进
requires。 requires使用and连接约束项,允许用括号组合子表达式,不支持or和 C++ 风格的任意布尔表达式模板。
泛型 concept
concept 名之后可以出现泛型参数列表。泛型 concept 描述“当前类型和其它类型之间”的静态关系:
cp
concept partial_eq<Rhs = this> {
equals(self const&, rhs: Rhs const&) -> bool;
}Rhs = this 让同类型比较成为默认约束,异类型比较仍可以显式写出右侧类型:
cp
requires
T: partial_eq
and T: partial_eq<str>在 concept 声明体内,泛型参数的作用域覆盖 requires、关联类型、函数要求和默认函数实现。this 表示当前实现类型;当 concept 被用于 T: partial_eq、impl partial_eq for i32 或 impl partial_eq for box<T> 时,this 分别绑定到 T、i32 和 box<T>。
参数包与 template for
参数包用于类型安全的变参泛型。只支持顺序展开,不支持 sizeof...、编译期索引、fold expression 或 pack pattern matching。
类型参数包写作:
cp
print<T...>(values: T...)
{
template for(let value : values...) {
write(value);
}
}规则:
T...在泛型参数列表中声明一个类型参数包。values: T...在函数参数列表中声明一个值参数包。- 值参数包长度与对应类型参数包长度相同。
- 参数包可以为空;为空时对应的
template forbody 展开零次。 - 值参数包必须位于普通参数列表末尾。
- 参数包不是数组、元组或 slice,不能整体存储、返回、赋值或作为普通表达式使用。
- 参数包只能在明确的展开上下文中使用,展开上下文只有
template for。
template for 遍历值参数包:
cp
log<T...>(values: T...)
{
template for(let value : values...) {
write(value);
}
}语义上,如果调用:
cp
log(1, "x", 2.0);则 template for 在实例化期按位置展开三次,每次展开体内的 value 分别绑定到当前实参。它不是运行时循环。
template for 也可以遍历类型参数包:
cp
register_all<T...>()
{
template for(type U : T...) {
register_type<U>();
}
}这里 U 是每次展开中的类型别名。
如果遍历值包时需要当前值的类型,使用 decltype(value):
cp
debug<T...>(values: T...)
{
template for(let value : values...) {
type U = decltype(value);
write_type_name<U>();
write(value);
}
}因此不设计“类型包和值包同步遍历”的特殊语法。值包迭代变量天然携带当前静态类型,decltype(value) 足以取出它。
template for 展开规则:
- 展开顺序从左到右。
- 每次展开 body 都建立独立作用域。
let value绑定当前值,const value绑定当前只读值。type U绑定当前类型。return仍然返回外层函数、lambda 或当前函数边界。break和continue不允许直接作用于template for,因为它不是运行时循环。- 展开后的语句按普通语义检查;依赖类型或依赖表达式可以延迟到实例化后检查。
参数包约束可以写在内联泛型参数或 requires 中:
cp
print<T...: display>(values: T...)
{
template for(let value : values...) {
write(value);
}
}等价于:
cp
print<T...>(values: T...)
requires
T...: display
{
template for(let value : values...) {
write(value);
}
}T...: display 表示参数包中每个类型都必须满足 display。多个 concept 仍使用 and:
cp
requires
T...: display and movable不支持对参数包写随机访问、长度查询或条件拆分:
cp
sizeof...(T) // unsupported in first stage
at...(values, 0) // unsupported in first stage
values...[0] // unsupported in first stage泛型 struct
struct 名之后可以出现泛型参数列表:
cp
struct vector<T> {
data: ptr<T>;
size: usize;
capacity: usize;
}泛型 struct 定义的是一个名义类型构造器。vector<i32> 和 vector<f64> 是不同的具体类型实例,但共享同一个泛型定义。
字段类型可以直接使用结构体泛型参数。结构体泛型参数的作用域覆盖整个 struct 体,不覆盖对应的 impl;impl 需要从自己的目标类型模式重新绑定参数。
用户自定义泛型类型支持类型参数和第一版整数 const 参数,因此可以声明 struct buffer<T, N: usize> 这种由值参数参与类型的名义类型。固定数组 [T; N] 是类型系统小内建,规则见 type_system.md。
泛型 variant
variant 名之后可以出现泛型参数列表,规则和泛型 struct 一致:
cp
variant optional<T> {
none;
some(T);
}
variant expected<T, E> {
value(T);
unexpected(E);
}泛型 variant 定义的是一个名义和类型构造器。optional<i32> 和 optional<f64> 是不同的具体类型实例,但共享同一个泛型定义。
case payload 类型可以直接使用 variant 的泛型参数。variant 泛型参数的作用域覆盖整个 variant 体,不覆盖对应的 impl;impl 需要从自己的目标类型模式重新绑定参数。
case 构造器挂在具体实例的类型命名空间下。不做 case 构造器类型实参推导,因此构造泛型 variant 时必须写满具体类型实参:
cp
let a = optional<i32>::none;
let b = optional<i32>::some(1);
let c = expected<i32, str>::unexpected("bad input");下面这些形式不支持:
cp
optional::some(1)
optional<_>::some(1)
expected<i32>::unexpected("bad input")match 面对泛型 variant 的具体实例时,先把类型参数替换为具体类型,再检查 case payload 绑定和分支类型统一:
cp
value_or<T>(value: optional<T>, fallback: T) -> T
{
return match value {
.some(v) => v,
.none => fallback,
};
}泛型 variant 的运行时布局按具体实例计算。optional<i32>、optional<str>、expected<i32,str> 分别拥有自己的 tag + payload storage 布局;tag 编号仍是编译器内部细节,不进入源语言语义。
泛型 impl
impl 使用 impl<...> 声明自己的泛型参数,随后在目标类型模式中使用这些参数:
cp
impl<T> vector<T> {
len(self const&) -> usize
{
return size;
}
}上例中,T 来自 impl<T>,作用域覆盖:
impl级requires子句。impl块内的成员函数、关联函数、构造函数和析构函数签名。impl块内的函数体。impl块内的type类型别名。
impl X<T> for Y<T> 这种写法中的两个 T 只有在 impl<T> 中声明后才表示同一个泛型参数;完整写法是 impl<T> X<T> for Y<T>。impl 头部是泛型作用域的入口,后续的目标类型模式、concept 实参和 requires 都引用这个作用域。
impl 级 requires 是条件实现。只有当具体类型实参满足约束时,这个 impl 块里的项才参与成员查找和关联项查找:
cp
impl<T> vector<T>
requires
T: movable
{
push_back(self&, value: T)
{
}
}含义是:
text
for every T:
if T implements movable:
vector<T> has this impl block因此 vector<i32> 是否拥有 push_back,取决于 i32 是否满足 movable。约束不满足时,不是在 impl 定义处报错,而是在使用点把该 impl 排除出候选;如果没有其它可用候选,则报告“没有满足约束的成员/关联项”,并指出失败的约束。
函数级 requires 只控制单个成员函数是否参与候选:
cp
impl<T> vector<T> {
contains(self const&, value: T const&) -> bool
requires
T: comparable
{
return false;
}
}vector<file> 如果不满足 file: comparable,则 contains 不参与 vector<file> 的成员查找。诊断应指向调用点,并说明候选存在但约束不满足。
定义阶段仍然检查非依赖错误。完全不依赖 T 或成员函数自身泛型参数的未知名字、语法错误、重复定义等,不允许延迟到实例化阶段。
impl 内部可以定义自己的泛型函数。成员函数泛型参数的作用域只覆盖该函数,不覆盖整个 impl:
cp
impl<T> vector<T> {
convert_all<U>(self const&) -> vector<U>
requires
T: convertible_to<U>
{
return vector<U>{};
}
}这里 T 来自 impl<T>,U 来自 convert_all<U>。
泛型 impl 中的构造函数和析构函数仍然使用类型构造器名,不写类型实参:
cp
impl<T> vector<T> {
vector()
{
return vector<T>{};
}
~vector()
{
}
}泛型 concept impl
impl<...> ConceptId for TypePattern 为一族类型提供 concept 证明,并可使用 requires 表达条件实现:
cp
impl<T> comparable for vector<T>
requires
T: comparable
{
less(self const&, rhs: this const&) -> bool
{
return self.len() < rhs.len();
}
}含义是:
text
if T implements comparable:
vector<T> implements comparable泛型 concept 的实参写在 concept 名后。省略时按 concept 声明中的默认泛型参数补齐:
cp
concept partial_eq<Rhs = this> {
equals(self const&, rhs: Rhs const&) -> bool;
}
impl<T> partial_eq for box<T>
requires
T: partial_eq
{
equals(self const&, rhs: box<T> const&) -> bool
{
return value.equals(rhs.value);
}
}这里 impl<T> partial_eq for box<T> 等价于 impl<T> partial_eq<box<T>> for box<T>,因为 partial_eq 的 Rhs 默认值是 this。
异类型关系可以显式写出 concept 实参:
cp
impl partial_eq<str> for string_like {
equals(self const&, rhs: str const&) -> bool
{
return true;
}
}与 concept 的关系
concept 描述静态能力:
cp
concept comparable {
less(self const&, rhs: this const&) -> bool;
}类型通过 impl concept for Type 证明自己满足能力:
cp
impl comparable for i32 {
less(self const&, rhs: this const&) -> bool
{
return self < rhs;
}
}泛型函数通过约束要求类型参数具备能力:
cp
min<T: comparable>(left: T, right: T) -> T
{
if(right.less(left)) {
return right;
}
return left;
}三者关系为:
text
concept comparable 描述能力
impl comparable for i32 证明 i32 有这个能力
T: comparable 要求泛型参数 T 有这个能力
impl<T> comparable for vector<T> requires T: comparable
条件证明 vector<T> 有这个能力无约束泛型仍然可以使用依赖操作,但 concept 约束让这些操作从“实例化时才发现是否成立”变成“公共接口上已经声明需要什么能力”。
函数类型实参推导
函数调用可以省略类型实参,由普通实参反推出泛型参数:
cp
add<T>(left: T, right: T) -> T
{
return left + right;
}
let value = add(1, 2); // 推导 T = i32,等价于 add<i32>(1, 2)
let other = add(1.0, 2.0); // 推导 T = f64,等价于 add<f64>(1.0, 2.0)没有函数重载,因此推导不参与重载排序。编译器先根据函数名找到唯一函数声明,再决定使用显式类型实参还是执行推导:
- 如果调用点写了显式泛型实参,它们按泛型参数列表前缀匹配;剩余参数由默认实参补齐。
- 如果调用点没有写显式泛型实参,编译器先从普通实参推导泛型参数,再用默认实参补齐仍未确定的参数。
- 不从返回类型、变量声明类型或其他上下文反推类型参数。
推导过程把形参类型当作模式,把实参表达式的已知类型当作目标类型:
cp
id<T>(value: T) -> T
{
return value;
}
let a = id(1); // T = i32当同一个类型参数出现多次时,所有位置必须推导出同一个具体类型:
cp
same<T>(left: T, right: T) -> T
{
return left;
}
let ok = same(1, 2); // T = i32
let bad = same(1, 2.0); // 错误:T 同时要求为 i32 和 f64推导阶段不做隐式转换。隐式转换只在类型实参已经确定之后,按普通调用检查规则处理。也就是说,same(1, 2.0) 不会为了推导 T 而先把 1 提升为 f64。
形参类型可以是包含类型参数的复合类型。推导时递归匹配类型结构:
cp
first<T, U>(value: (T, U)) -> T
{
return value.0;
}
let x = first((1, 2.0)); // T = i32, U = f64递归匹配规则:
- 形参模式是类型参数
T时,把T绑定到当前目标类型。 - 形参模式和目标类型是同一种复合类型时,递归匹配其类型实参,例如
(T, U)对(i32, f64)。 [T; 3]可以从[i32; 3]推导出T = i32。[T; N]可以从[i32; 4]推导出T = i32, N = 4,其中N必须是当前函数可见的usize或isize整数 const 参数。- 引用、指针和
const修饰必须按同一类型结构递归匹配,例如T const&可以从i32 const&推导出T = i32。
参数包推导只支持函数参数列表末尾的值参数包:
cp
print<T...>(values: T...)
{
template for(let value : values...) {
write(value);
}
}
print(1, "x", 2.0); // T... = i32, str, f64推导规则:
values: T...匹配调用点剩余的全部普通实参。- 每个实参按当前位置推导一个类型实参,形成
T...。 - 参数包可以推导为空包。
- 如果函数还有普通形参,普通形参先按位置匹配,剩余实参进入末尾参数包。
- 不支持非末尾参数包,也不支持一个函数参数列表中多个值参数包。
- 包内类型推导不做隐式转换,和普通泛型推导一致。
如果某个类型参数无法从普通实参推出,调用必须显式写出全部类型实参:
cp
make<T>() -> T
{
return T{};
}
let a = make<i32>(); // 合法
let b: i32 = make(); // 错误:不从返回上下文推导 Tconcept 和 requires 约束不参与反向推导,只在类型实参确定后检查:
cp
copy_all<I: iterator, O: sink>(input: I, output: O)
requires
I::iter_item == O::sink_item
{
}这里 I 和 O 只能从 input、output 的实参类型推导;I::iter_item == O::sink_item 用于验证推导结果,而不是产生新的类型参数绑定。
实例化语义
泛型不是运行时多态。后端采用单态化:
cp
add<T>(left: T, right: T) -> T
{
return left + right;
}
main() -> i32
{
return add(1, 2);
}add(1, 2) 推导出 T = i32,编译器生成或复用 add<i32> 的实例。
函数实例化流程建议为:
- 解析并保存泛型函数 AST、类型参数表和约束表达式。
- 调用点根据显式类型实参或普通实参推导类型参数。
- 检查显式 concept /
requires约束。 - 用具体类型替换类型参数。
- 对替换后的函数体执行完整语义检查,包括依赖 UFCS 调用。
- 缓存已成功实例化的版本,避免重复生成。
如果实例化失败,诊断应同时指出调用点和泛型函数体中失败的依赖操作。
泛型 struct、泛型 variant 和 impl 的实例化规则与函数一致:先由具体使用点确定类型实参,再检查 requires 约束,最后对依赖函数体执行完整语义检查。条件 impl 的约束不满足时,该 impl 不生成对应具体实例,也不向该具体类型贡献成员或 concept 证明。
支持内容
泛型支持:
- 类型参数:
func<T, U>(...) - 强无约束泛型:允许依赖操作,实例化时检查
- 内联 concept 约束:
T: comparable - 多 concept 约束:
T: readable and writable requires子句- 类型相等约束:
I::iter_item == T - 依赖
decltype(expr):实例化后确定表达式类型 - 参数包:
func<T...>(values: T...) - 参数包约束:
T...: display - 整数 const 参数:
N: usize/I: isize template for(let value : values...)值包展开template for(type U : T...)类型包展开- 依赖 UFCS 调用实例化时检查
- 函数类型实参推导:从普通实参推导,支持嵌套类型模式
- 泛型
struct:struct vector<T> - 默认泛型参数:
struct pair<T, U = T> - 带整数 const 参数的泛型
struct:struct buffer<T, N: usize> - 泛型
variant:variant optional<T> - 泛型
concept:concept partial_eq<Rhs = this> - 泛型 concept 默认参数中的
this绑定 - 泛型固有
impl:impl<T> vector<T> impl级条件约束:impl<T> vector<T> requires T: movable- 成员泛型函数:
convert_all<U>(...) - 泛型 concept
impl:impl<T> comparable for vector<T> requires T: comparable - 显式 concept 实参:
impl partial_eq<str> for string_like - 单态化代码生成