一、trait
trait有点类似go的interface,但和go的接口有点不一样,他是静态分发的。静态分发可能有点不好理解。下面聊dyn就明白了。
rust里面的trait可以实现多态。
1.定义trait
定义直接使用trait后跟trait
1
2
3
4
|
// work trait
trait Person {
fn work(&self);
}
|
2.给类型实现trait里面的方法
这里定义了两个角色Programmer
和Teacher
,他们都实现了work
方法。
语法impl
trait_name for
struct_name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
// 程序员
struct Programmer {}
// 老师
struct Teacher {}
// 给老师实现work方法
impl Person for Teacher {
fn work(&self) {
println!("Teacher:每年有寒暑假");
}
}
// 给程序员实现work方法
impl Person for Programmer {
fn work(&self) {
println!("Programmer:每周有996");
}
}
|
3. 使用trait
由于我们给Programmer
和Teacher
都实现work,现在在一个包装函数里面调用就行,理想化是参数不同,输出不同。
先使用arg_name:impl trait_name
声明函数参数。p.work()
调用,就完事
1
2
3
4
5
6
7
8
9
|
// p是一个实现Person函数
fn do_work(p: impl Person) {
p.work()
}
fn main() {
do_work(Programmer {});
do_work(Teacher {});
}
|
二、dyn
如果说rust什么最像go的interface,那一定是dyn。为什么需要dyn。修改下上面的例子,以年龄作为分界线,之前做Programmer,之后当Teacher。
这里稍作准备,给每个角色加age。然后洋洋洒洒写下如下代码 if else 生成不同的对象,然后运行。
1
2
3
4
5
6
7
|
fn select_person(age: i32) -> impl Person {
if age <= 35 {
Programmer {}
} else {
Teacher {}
}
}
|
1.报错
然后不出意外,又被rust编译器打脸。
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
|
error[E0308]: `if` and `else` have incompatible types
--> src/bin/dyn-trait.rs:36:9
|
33 | / if age <= 35 {
34 | | Programmer {}
| | ------------- expected because of this
35 | | } else {
36 | | Teacher {}
| | ^^^^^^^^^^ expected struct `Programmer`, found struct `Teacher`
37 | | }
| |_____- `if` and `else` have incompatible types
|
help: you could change the return type to be a boxed trait object
|
32 | fn select_person(age: i32) -> Box<dyn Person> {
| ^^^^^^^ ^
help: if you change the return type to expect trait objects, box the returned expressions
|
34 | Box::new(Programmer {})
35 | } else {
36 | Box::new(Teacher {})
|
error: aborting due to previous error
|
从上面的报错信息,可以看出,impl Person。已经实现为Programmer。else部分找到Teacher。然后编译器和贴心,也给出修改方法。
2.修改之后的代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
fn dyn_do_work(p: Box<dyn Person>) {
p.work();
}
fn select_person(age: i32) -> Box<dyn Person> {
if age <= 35 {
Box::new(Programmer { age: age })
} else {
Box::new(Teacher { age: age })
}
}
fn main() {
dyn_do_work(select_person(30));
dyn_do_work(select_person(36));
}
|
ok,成功运行。rust
编译器需要知道每个函数的返回类型需要多大。所以函数编译之后就需要一个具体的类型。
我们在上面if else
代码块里面返回两个
不同类型,他们都实现trait
。在编译之后会报错,选择第一个类型,类型固化就和第二个类型不一样,这也是上面报错的原因。 在函数里面动态选择不同实现是个很常见的需求,所以rust使用dyn
关键字解决。Box返回指向堆的指针。绕过类型不一样的限制。
dyn
作为关键字,在语义上和trait
有所区分。简单来说一个静态分发
,一个动态分发
。
三、完整代码地址如下
https://github.com/guonaihong/rust-example/tree/main/trait-example