泛型系统

本文档收敛 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 的类型只有:

  • int
  • bool
  • rune
  • string

第一阶段不支持:

  • 多重约束
  • where 子句
  • 用户自定义约束
  • 基于方法集的复杂约束系统
  • 恢复旧的 comparable 设计作为通用约束

与参数化容器类型的关系

内建参数化类型:

  • list<T>
  • map<K, V>
  • opt<T>
  • iter<T>

和用户定义泛型共用同一套类型参数语法,但它们的具体语义仍由各自的专题文档定义,不由本文件重复展开。

这些内建参数化类型不是用户代码定义出来的普通泛型 struct,而是语言内建类型。

因此:

  • 语法一致
  • 类型参数写法一致
  • 但具体操作语义由各自专题定义

例如:

  • list<T>push/pop/remove
  • opt<T>none/some/is
  • iter<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 不会因为“是泛型”而自动获得额外能力。

例如,泛型本身不会自动决定:

  • 是否可比较
  • 是否可作 map key
  • 是否可迭代
  • 是否具有特殊内建方法

这些能力仍由语言内建规则或显式约束单独定义。

当前不讨论的实现问题

本文不规定:

  • 单态化还是共享实现
  • Rust 代码生成的具体映射方式
  • 运行时表示
  • trait/object 风格的底层实现策略

这些属于编译器实现问题,不能反向决定语言语义。

当前结论

Dujie 第一阶段的泛型系统可以概括为:

  • 只支持顶层 func 和顶层 struct 泛型
  • 类型位置显式写实参
  • 函数调用位置做局部推导
  • 泛型 struct 构造可从字段值局部推导
  • 约束系统只保留最小的 key