特征对象

在特征练习中 我们已经知道当函数返回多个类型时,impl Trait 是无法使用的。

对于数组而言,其中一个限制就是无法存储不同类型的元素,但是通过之前的学习,大家应该知道枚举可以在部分场景解决这种问题,但是这种方法局限性较大。此时就需要我们的主角登场了。

使用 dyn 返回特征

Rust 编译器需要知道一个函数的返回类型占用多少内存空间。由于特征的不同实现类型可能会占用不同的内存,因此通过 impl Trait 返回多个类型是不被允许的,但是我们可以返回一个 dyn 特征对象来解决问题。

  1. 🌟🌟🌟
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. 🌟🌟
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

  1. 🌟🌟
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语言圣经。

  1. 🌟🌟
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 路径下)