Skip to content

05 Generics

此部分介绍 YIAN 中的泛型系统, 包括泛型函数/方法, 泛型类型, 以及相关的概念和语法.

YIAN 的泛型是纯静态的, 也就是说, 泛型参数必须在编译时被具体化为一个具体的类型, 从而生成对应的函数/方法/类型实例.

Generic Structs/Enums

一个泛型 struct 和 enum 的实例如下:

struct Point<T> {
    T x
    T y
}

enum Option<T> {
    None
    Some { T val }
}

实例化这些类型时, 不仅需要提供类型名称, 还需要指定类型参数:

Point<i32> p = Point<i32>(1, 2)
Option<i32> o = Option<i32>.Some(42)

唯一的例外是构造泛型 struct 时, 可以省略类型参数, 由编译器根据构造函数参数的类型推断出类型参数:

Point<i32> p = Point(1, 2) // 编译器推断出 T 是 i32

Generic Traits

一个泛型 trait 的实例如下:

trait Addable<T> {
    T add(T other)
}

注意: 其中包含的方法 add 也是一个泛型方法, 因为它的返回类型和参数类型都依赖于 trait 的类型参数 T.

通过 impl 块可以将一个泛型 trait 实例化, 并为特定类型实现该 trait:

impl Addable<i32> for i32 {
    i32 add(i32 other) {
        return *self + other
    }
}

Generic Functions

一个泛型函数的实例如下:

T identity<T>(T x) {
    return x
}

在函数名后指定了类型参数之后, 函数的返回值, 参数列表, 以及函数体都会处于泛型上下文中, 在这个上下文中, 可以将类型参数当作一个普通的类型来使用. 泛型函数在被调用时, 会根据调用处指定的类型实参来生成对应的函数实例:

i32 a = identity<i32>(42) // 调用 identity 函数, 指定 T 为 i32
str s = identity("Hello") // 调用 identity 函数, 省略类型参数, 由编译器推断出 T 是 str

Generic Methods

泛型方法的定义比较特殊, 简单来说, 如果一个方法满足下列其中一个:

  • 自身包含类型参数
  • 定义在一个泛型上下文中

那么它就是一个泛型方法. 例如, 在Generic Traits章节中定义的 add 方法就是一个泛型方法, 因为它定义在一个泛型 trait 中.

再例如:

impl str {
    T parse<T>() {
        T.from_str(*self)
    }
}

在这个例子中, parse 方法是一个泛型方法, 因为它自身包含类型参数 T.

泛型方法在被调用时, 需要进行实例化, 但是由于泛型方法的类型参数既可以来自于方法自身, 也可以来自于所在的泛型上下文, 因此在调用时, 两种类型参数确定的方法是不同的:

  • 对于方法自身的类型参数: 在调用时, 需要显式指定类型实参, 例如 s.parse<i32>(), 也可以省略类型实参, 由编译器根据调用上下文推断出类型实参(前提是参数的信息足够)
  • 对于泛型上下文中的类型参数: 在调用时, 根据方法的调用者确定, 不需要显式指定

Generic Impls

泛型 impl 是指拥有自己的类型参数的 impl 块, 例如:

impl<T> Point<T> {
    T x() {
        return self.x
    }
}

在这个例子中, impl 块拥有一个类型参数 T, 从而创建了一个泛型上下文, 这个 impl 块的目标类型 Point<T> 就是使用上下文中的 T 类型实例化出来的. 对于 impl for 的语法同理.

泛型 impl 不仅仅为某个特定的类型实现方法, 而是为一个类型族实现方法. 在上面的例子中, 这个类型族就是能够从 Point<T> 实例化出来的所有类型, 例如 Point<i32>, Point<f64>, Point<str> 等等. 下面是一个更复杂的例子:

trait Summable<T> {
    T sum()
}

struct Pair<T, U> {
    T first
    U second
}

impl<T> Summable<T> for Pair<T, T> {
    T sum() {
        return self.first + self.second
    }
}

在这个例子中, impl 块为 Pair<T, T> 这个类型族实现了 Summable<T> trait, 也就是说, 只要 Pair 的两个类型参数相同, 就可以调用 sum 方法来计算它们的和:

Pair<i32, i32> p1 = Pair(1, 2)
i32 result1 = p1.sum() // 调用 sum 方法, 结果是 3
Pair<i32, f64> p2 = Pair(1, 2.0)
// i32 result2 = p2.sum() // 编译错误, 因为 Pair<i32, f64> 不满足 Pair<T, T> 的条件