写点什么

每日一 R「08」类型系统(二)

作者:Samson
  • 2022 年 8 月 16 日
    上海
  • 本文字数:2197 字

    阅读完需:约 7 分钟

每日一R「08」类型系统(二)

今天我们将继续学习多态的另外两种方式:特设多态和子类型多态。首先来简单回顾一下:特设多态指同一种行为有很多不同的实现,例如面向对象语言中的方法重载;子类型多态是指子类型可以当作父类型使用,例如面向对象语言中的里氏替换原则。


Rust 中特设多态和子类型多态的实现都与 trait 相关,trait 可以类比于 Java 中的接口。例如:


trait Num {    fn from_i32(n: i32) -> Self;}
复制代码


Trait 中的方法可以有缺省实现。为某个数据结构实现特定 trait 时,可以重新定义、覆盖缺省实现,但必须实现仅声明却未实现的关联方法(关于关联方法的介绍,参考“03-扩展”)。

01-特设多态

Trait 将数据结构中的行为抽取出来,使其可以在多个类型之间共享,也可作为类型约束,限制传入参数必须具备某些行为(实现特定的 trait)。


如前面介绍的那样,基本 trait 比较容易理解,可以类比其他语言中的概念来理解。Trait 也支持一些高级用法,例如关联类型。


在 trait 中可以声明关联类型,也可以通过类型限定语法: xxx限定关联类型。例如:


trait Container {    type E: Debug;    fn empty() -> Self;    fn insert(&mut self, elem: Self::E);}impl<T: Debug> Container for Vec<T> {    type E = T;    fn empty() -> Vec<T> { Vec::new() }    fn insert(&mut self, x: T) { self.push(x); }}
复制代码


Trait 中的 “type E” 是声明,”: Debug” 是对 E 的类型限定,即 E 必须实现了 Debug trait。impl 中 “type E = T” 指定了 E 的具体类型。关联类型允许用户把 trait 中使用的类型延迟到实现 trait 时,而非声明时。因此,带有关联类型的 trait 比基本 trait 更灵活,抽象度也更高。


Trait 也可以支持泛型。Trait 为什么需要支持泛型?考虑如下场景,如果我们有一个自定义结构 Complex(复数),我们想为它实现 Add trait,让其支持与 Complex 相加,应该怎么定义?可以按照如下实现:


trait Add {    type E;    pub fn add(self, other: E) -> Self;}impl Add for Complex {    type E = Self;    pub fn add(self, other: Self) -> Self {        ...    }}
复制代码


但如果我们想实现 Complex 与 u32 相加呢?怎么实现呢?虽然用上面的方式也能够实现类似的功能,但需要写的代码就非常多,也不够优雅。


带泛型参数的 trait 在这个情形下就非常有用了。上面的代码可以修改为:


trait Add<Rhs = Self> {    type Output;    fn add(self, rhs: Rhs) -> Self::Output;}// 如果不指定泛型,则默认为 Complex 类型impl Add for Complex {}// 指定泛型,意为 Complex + u32impl Add<u32> for Complex {    ...}
复制代码


Rust 中,trait 可以“继承”另外一个 trait 中声明的关联函数和关联类型,即在自己的声明中使用“继承”来的关联项。例如,trait B: A 表示任何类型 T,如果实现了 trait B,也必须实现 trait A。考虑如下示例:


impl<T: ?Sized> StreamExt for T where T: Stream { ... }
复制代码


意思是为所有实现了 Stream trait 的类型 T 实现 StreamExt trait。所以,如果某个类型实现了 Stream trait,它也一定实现了 StreamExt trait。


通过定义 trait 以及为不同的类型实现这个 trait,我们就已经实现了特设多态。

02-子类型多态

虽然 Rust 中不支持继承,但是 trait 与实现 trait 的类型之间是类似的关系。在对函数入参进行类型限定时,可以使用”impl xxx” 来限定参数的具体必须实现 xxx trait。以下两种写法是等价的:


fn name<T: Animal>(animal: T) -> &'static str { ... }fn name(animal: impl Animal) -> &'static str { ... }
复制代码


但是这种本质上是泛型,所以会在编译时进行单态化处理。但有时编译时并不知道运行时的具体类型,所以需要一种方式可以告诉编译器,这是实现了某个 trait 的类型,但并不知道具体的类型。Rust 中使用 trait object 实现,语法是 &dyn Trait 或者 Box<dyn Trait>。这种方式是动态分派。

03-扩展

关联项(Associated Items)指与某个类型关联的项。主要包括三类:关联函数、关联类型和关联常量。


Associated Items  are the items declared in traits or defined in implementations. They are called this because they are defined on an associate type — the type in the implementation.


关联函数是指于某个类型关联的函数。根据关联项的定义,关联函数指 trait 中声明或 impl 中定义的函数,其关联的类型即实现 trait 的数据结构或 impl … for 指定的数据结构。例如:


struct Struct {    field: i32}
impl Struct { fn new() -> Struct { Struct { field: 0i32 } }}
trait Num { fn from_i32(n: i32) -> Self;}
impl Num for f64 { fn from_i32(n: i32) -> f64 { n as f64 }}
复制代码


new 是 Struct 的关联函数,调用方式为 Struct::new(),from_i32 是 f64 的关联函数,调用方式为 f64::from_i32()。


如果关联函数的第一个参数是 Self / self / &self / &mut self,也称关函数为方法(method)。方法可以通过 xxx.function_name() 形式的语法调用。


[1] Associated Items


本节课程链接:《13|类型系统:如何使用trait来定义接口?


历史文章推荐

每日一 R「07」类型系统(一)

每日一 R「06」内存管理

每日一 R「05」生命周期

每日一 R「04」常用的智能指针

每日一 R「03」Borrow 语义与引用

每日一 R「02」所有权与 Move 语义

每日一 R「01」跟着大佬学 Rust

发布于: 刚刚阅读数: 3
用户头像

Samson

关注

还未添加个人签名 2019.07.22 加入

还未添加个人简介

评论

发布
暂无评论
每日一R「08」类型系统(二)_8月月更_Samson_InfoQ写作社区