参考章节《Rust 程序设计语言》第10.2章 Trait:定义共同行为
trait
类似其他语言中的 接口
的概念,我们可以通过 trait
以一种抽象的方式定义共享的行为。
书中这一段解释得很好,建议多读几次
一个类型的行为由其可供调用的方法构成。如果可以对不同类型调用相同的方法的话,这些类型就可以共享相同的行为了。
trait
定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。
我们来看一个简单的例子
我们定义一个 Usb trait
,它有两个方法 insert
和 remove
所有实现 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);
}
}
}
|
trait
类似接口,用于定义一组在不同类型间共享的行为
trait
可以有默认实现
- 一个类型可以实现多个
trait
trait bound
可以用在多个地方,可以用在函数参数上,限制参数类型
,也可以用在 impl 关键字后用以限制实现的条件
where
可以简化 trait bound