Rust 学习笔记(20)-trait

参考章节《Rust 程序设计语言》第10.2章 Trait:定义共同行为

trait 类似其他语言中的 接口 的概念,我们可以通过 trait 以一种抽象的方式定义共享的行为。

书中这一段解释得很好,建议多读几次

一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。
trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。

我们来看一个简单的例子

我们定义一个 Usb trait,它有两个方法 insertremove 所有实现 Usb trait 的类型默认都必须实现这两个方法
定义接口使用 trait 关键字,内部方法如果不需要默认实现,则可以用 ; 号结尾
实现接口使用 impl YYY for XXX 读为 为XXX类型实现YYY接口(trait)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Usb 接口
pub trait Usb {
    fn insert(&self); // 插入
    fn remove(&self); // 移除
}

// U盘
pub struct Upan {}

// 为U盘实现Usb接口功能
impl Usb for Upan {
    fn insert(&self) {
        println!("U盘被插入");
    }
    fn remove(&self) {
        println!("U盘被拔出");
    }
}

// 鼠标
pub struct Mouse {}

// 为鼠标实现Usb接口功能
impl Usb for Mouse {
    fn insert(&self) {
        println!("Usb鼠标已连接");
    }
    fn remove(&self) {
        println!("Usb鼠标已拔出");
    }
}

默认实现

我们的 trait 可以提供默认实现,这样实现者可以选择使用默认行为还是自己实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
pub trait Usb {
    fn insert(&self) {
        println!("Usb已连接");
    }
    fn remove(&self) {
        println!("Usb已移除");
    }
}

// 键盘
pub struct Keyboard {}

// 如果想要使用默认实现,而不是定义一个自己的实现,则可以指定一个空的 impl 块。
impl Usb for Keyboard {} // 使用默认实现

trait 作为参数

trait 作为参数可以限制参数的类型必须是实现了指定 trait 的类型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
// 该函数支持任何实现了 Usb trait 的类型。
// 这里为了演示返回值的写法,让这个函数返回了所有权,实际这行代码并没有任何意义
fn usb_debug(device: impl Usb) -> impl Usb {
        device.insert();
        println!("启用Usb调试");
        device.remove();
        device
}
*/


// 推荐使用 Trait Bound 语法糖,它等价于上面的写法
// 这里为了演示返回值的写法,让这个函数返回了所有权,实际这行代码并没有任何意义
fn usb_debug<T: Usb>(device: T) -> T {
        device.insert();
        println!("启用Usb调试");
        device.remove();
        device
}

fn main() {
    let u = Upan {};
    let m = Mouse {};

    usb_debug(u);
    usb_debug(m);
}

一个类型可以实现多个 trait,一个函数也可以通过 + 号指定多个 trait

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
pub trait Usb {
    fn insert(&self) {
        println!("Usb已连接");
    }
    fn remove(&self) {
        println!("Usb已移除");
    }
}

// 提供读写能力
pub trait ReadWrite {
    fn read(&self) -> String;
    fn write(&self, data: &str);
}

// U盘
pub struct Upan {}

// 为U盘实现Usb接口功能
impl Usb for Upan {
    fn insert(&self) {
        println!("U盘被插入");
    }
    fn remove(&self) {
        println!("U盘被拔出");
    }
}

// 为U盘实现读写功能
impl ReadWrite for Upan {
    fn read(&self) -> String {
        String::from("Hello World!")
    }

    fn write(&self, data: &str) {
        println!("写入数据 {} 到U盘成功.", data);
    }
}

/*
// 通过+号指定多个trait
fn usb_read(device: impl Usb + ReadWrite) {
        device.insert();
        let data = device.read();
        println!("读取到数据: {}", data);
        device.remove();
}
*/

// Trait Bound 语法糖
fn usb_read<T: Usb + ReadWrite>(device: T) {
        device.insert();
        let data = device.read();
        println!("读取到数据: {}", data);
        device.remove();
}

通过 where 简化 trait bound

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
/*
fn usb_read<T: Usb + ReadWrite>(device: T) {
        device.insert();
        let data = device.read();
        println!("读取到数据: {}", data);
        device.remove();
}
*/

// 它等价于上面的写法
// 这个函数签名就显得不那么杂乱,函数名、参数列表和返回值类型都离得很近,看起来跟没有那么多 trait bounds 的函数很像。
fn usb_read<T>(device: T)
where
    T: Usb + ReadWrite,
{
    device.insert();
    let data = device.read();
    println!("读取到数据: {}", data);
    device.remove();
}

有条件的实现方法

下面的代码来自于书中使用 trait bound 有条件地实现方法这一小节

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

// 为所有类型都实现了 new 函数
impl<T> Pair<T> {
    // 关联函数
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

// 只有那些为 T 类型实现了 PartialOrd trait (来允许比较) 和 Display trait (来启用打印)的 Pair<T> 才会实现 cmp_display 方法
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}

总结一下

  1. trait 类似接口,用于定义一组在不同类型间共享的行为
  2. trait 可以有默认实现
  3. 一个类型可以实现多个 trait
  4. trait bound 可以用在多个地方,可以用在函数参数上,限制参数类型,也可以用在 impl 关键字后用以限制实现的条件
  5. where 可以简化 trait bound
updatedupdated2025-03-012025-03-01