认识「泛型 & Trait」

1,557 阅读2分钟

「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战


泛型和trait是Rust类型系统中最重要的两个概念。

泛型并不是Rust特有的概念,在很多强类型编程语言中也支持泛型。泛型允许开发者编写一些在使用时才指定类型的代码。

Rust 中的 trait,它借鉴了Haskell的Typeclass。同时也是:Rust 实现零成本抽象的基石

  1. trait是Rust唯一的接口抽象方式
  2. 可以静态分发,也可以动态调用
  3. 可以当作标记类型拥有某些特定行为的"标签"来使用

最关键的一句话:trait 是对类型行为的抽象。

泛型

在泛型的类型签名中,通常使用字母T来代表一个泛型。也就是说这个Option<T>枚举类型对于任何类型都适用。

这样的话,我们就没必要给每个类型都定义一遍Option枚举,比如 Option<u32>或 Option<String>等。标准库提供的 Option<T> 类型已经通过 use std::prelude::v1::* 自动引入了每个Rust包中,所以可以直接使用 Some(T)/None 来表示一个 Option<T> 类型,而不需要写 Option::Some(T)Option::None

Trait

pub trait Fly {
    fn fly(&self) -> bool;
}
struct Duck;
struct Pig;
// impl block
impl Fly for Duck {
    fn fly(&self) -> bool {
        return true;
    }
}
impl Fly for Pig {
    fn fly(&self) -> bool {
        return false;
    }
}
// use Triat
fn fly_static<T: Fly>(s: T) -> bool {
    s.fly()
}
fn fly_dyn(s: &Fly) -> bool {
    s.fly()
}

fn main() {
	let pig = Pig;
	assert_eq!(fly_static::<Pig>(pig), false);
	let duck = Duck;
	assert_eq!(fly_static::<Duck>(duck), true);
	assert_eq!(fly_dyn(&Pig), false);
	assert_eq!(fly_dyn(&Duck), true);
}

我们需要关注两个点:

fly_static()

我们可以看到:fly_static<T: Fly>(s: T) 。其中:T:Fly 这种语法形式使用 Fly Trait 对泛型T进行行为上的限定 → 代表实现 Fly Trait 的类型,或者是拥有 fly 这种行为的类型

这种限制在Rust中被成为:Trait bound

通过Trait bound,限制了fy_static泛型函数参数的类型范围。如果有不满足该限定的类型传入,编译器就会识别并报错。

fy_static::<Pig>

使用了assert!断言,用于判断 fy_static::<Pig>(pig) 的调用结果是否将会返回 false。其中 ::<Pig> 这样的语法形式用于给泛型函数指定具体的类型,这里调用的是 Pig实现的fly 方法。

上面这种调用方式在 Rust 中叫静态分发。

Rust 编译器会为 fy_static::<Pig>(pig)和 fy_static::<Duck>(duck)这两个具体类型的调用生成特殊化的代码。也就是说,对于编译器来说,这种抽象并不存在,因为在编译阶段,泛型已经被展开为具体类型的代码。