特征对象
在特征练习中 我们已经知道当函数返回多个类型时,impl Trait 是无法使用的。
对于数组而言,其中一个限制就是无法存储不同类型的元素,但是通过之前的学习,大家应该知道枚举可以在部分场景解决这种问题,但是这种方法局限性较大。此时就需要我们的主角登场了。
使用 dyn 返回特征
Rust 编译器需要知道一个函数的返回类型占用多少内存空间。由于特征的不同实现类型可能会占用不同的内存,因此通过 impl Trait 返回多个类型是不被允许的,但是我们可以返回一个 dyn 特征对象来解决问题。
- 🌟🌟🌟
2. trait Bird {
3. fn quack(&self) -> String;
4. }
6. struct Duck;
7. impl Duck {
8. fn swim(&self) {
9. println!("Look, the duck is swimming")
10. }
11. }
12. struct Swan;
13. impl Swan {
14. fn fly(&self) {
15. println!("Look, the duck.. oh sorry, the swan is flying")
16. }
17. }
19. impl Bird for Duck {
20. fn quack(&self) -> String{
21. "duck duck".to_string()
22. }
23. }
25. impl Bird for Swan {
26. fn quack(&self) -> String{
27. "swan swan".to_string()
28. }
29. }
31. fn main() {
32. // 填空
33. let duck = __;
34. duck.swim();
36. let bird = hatch_a_bird(2);
37. // 变成鸟儿后,它忘记了如何游,因此以下代码会报错
38. // bird.swim();
39. // 但它依然可以叫唤
40. assert_eq!(bird.quack(), "duck duck");
42. let bird = hatch_a_bird(1);
43. // 这只鸟儿忘了如何飞翔,因此以下代码会报错
44. // bird.fly();
45. // 但它也可以叫唤
46. assert_eq!(bird.quack(), "swan swan");
48. println!("Success!")
49. }
51. // 实现以下函数
52. fn hatch_a_bird...
在数组中使用特征对象
- 🌟🌟
1. trait Bird {
2. fn quack(&self);
3. }
5. struct Duck;
6. impl Duck {
7. fn fly(&self) {
8. println!("Look, the duck is flying")
9. }
10. }
11. struct Swan;
12. impl Swan {
13. fn fly(&self) {
14. println!("Look, the duck.. oh sorry, the swan is flying")
15. }
16. }
18. impl Bird for Duck {
19. fn quack(&self) {
20. println!("{}", "duck duck");
21. }
22. }
24. impl Bird for Swan {
25. fn quack(&self) {
26. println!("{}", "swan swan");
27. }
28. }
30. fn main() {
31. // 填空
32. let birds __;
34. for bird in birds {
35. bird.quack();
36. // 当 duck 和 swan 变成 bird 后,它们都忘了如何翱翔于天际,只记得该怎么叫唤了。。
37. // 因此,以下代码会报错
38. // bird.fly();
39. }
40. }
&dyn and Box
- 🌟🌟
2. // 填空
3. trait Draw {
4. fn draw(&self) -> String;
5. }
7. impl Draw for u8 {
8. fn draw(&self) -> String {
9. format!("u8: {}", *self)
10. }
11. }
13. impl Draw for f64 {
14. fn draw(&self) -> String {
15. format!("f64: {}", *self)
16. }
17. }
19. fn main() {
20. let x = 1.1f64;
21. let y = 8u8;
23. // draw x
24. draw_with_box(__);
26. // draw y
27. draw_with_ref(&y);
29. println!("Success!")
30. }
32. fn draw_with_box(x: Box<dyn Draw>) {
33. x.draw();
34. }
36. fn draw_with_ref(x: __) {
37. x.draw();
38. }
静态分发和动态分发Static and Dynamic dispatch
关于这块内容的解析介绍,请参见 Rust语言圣经。
- 🌟🌟
2. trait Foo {
3. fn method(&self) -> String;
4. }
6. impl Foo for u8 {
7. fn method(&self) -> String { format!("u8: {}", *self) }
8. }
10. impl Foo for String {
11. fn method(&self) -> String { format!("string: {}", *self) }
12. }
14. // 通过泛型实现以下函数
15. fn static_dispatch...
17. // 通过特征对象实现以下函数
18. fn dynamic_dispatch...
20. fn main() {
21. let x = 5u8;
22. let y = "Hello".to_string();
24. static_dispatch(x);
25. dynamic_dispatch(&y);
27. println!("Success!")
28. }
对象安全
一个特征能变成特征对象,首先该特征必须是对象安全的,即该特征的所有方法都必须拥有以下特点:
- 返回类型不能是
Self. -
不能使用泛型参数
-
🌟🌟🌟🌟
2. // 使用至少两种方法让代码工作
3. // 不要添加/删除任何代码行
4. trait MyTrait {
5. fn f(&self) -> Self;
6. }
8. impl MyTrait for u32 {
9. fn f(&self) -> Self { 42 }
10. }
12. impl MyTrait for String {
13. fn f(&self) -> Self { self.clone() }
14. }
16. fn my_function(x: Box<dyn MyTrait>) {
17. x.f()
18. }
20. fn main() {
21. my_function(Box::new(13_u32));
22. my_function(Box::new(String::from("abc")));
24. println!("Success!")
25. }
你可以在这里找到答案(在 solutions 路径下)
