进一步深入特征

关联类型

关联类型主要用于提升代码的可读性,例如以下代码 :

1. #![allow(unused)]
2. fn main() {
3. pub trait CacheableItem: Clone + Default + fmt::Debug + Decodable + Encodable {
4. type Address: AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash;
5. fn is_null(&self) -> bool;
6. }
7. }

相比 AsRef<[u8]> + Clone + fmt::Debug + Eq + Hash, Address 的使用可以极大的减少其它类型在实现该特征时所需的模版代码.

  1. 🌟🌟🌟
2. struct Container(i32, i32);

4. // 使用关联类型实现重新实现以下特征
5. // trait Contains {
6. //    type A;
7. //    type B;

9. trait Contains<A, B> {
10. fn contains(&self, _: &A, _: &B) -> bool;
11. fn first(&self) -> i32;
12. fn last(&self) -> i32;
13. }

15. impl Contains<i32, i32> for Container {
16. fn contains(&self, number_1: &i32, number_2: &i32) -> bool {
17. (&self.0 == number_1) && (&self.1 == number_2)
18. }
19. // Grab the first number.
20. fn first(&self) -> i32 { self.0 }

22. // Grab the last number.
23. fn last(&self) -> i32 { self.1 }
24. }

26. fn difference<A, B, C: Contains<A, B>>(container: &C) -> i32 {
27. container.last() - container.first()
28. }

30. fn main() {
31. let number_1 = 3;
32. let number_2 = 10;

34. let container = Container(number_1, number_2);

36. println!("Does container contain {} and {}: {}",
37. &number_1, &number_2,
38. container.contains(&number_1, &number_2));
39. println!("First number: {}", container.first());
40. println!("Last number: {}", container.last());

42. println!("The difference is: {}", difference(&container));
43. }

定义默认的泛型类型参数

当我们使用泛型类型参数时,可以为该泛型参数指定一个具体的默认类型,这样当实现该特征时,如果该默认类型可以使用,那用户再无需手动指定具体的类型。

  1. 🌟🌟
2. use std::ops::Sub;

4. #[derive(Debug, PartialEq)]
5. struct Point<T> {
6. x: T,
7. y: T,
8. }

10. // 用三种方法填空: 其中两种使用默认的泛型参数,另外一种不使用
11. impl __ {
12. type Output = Self;

14. fn sub(self, other: Self) -> Self::Output {
15. Point {
16. x: self.x - other.x,
17. y: self.y - other.y,
18. }
19. }
20. }

22. fn main() {
23. assert_eq!(Point { x: 2, y: 3 } - Point { x: 1, y: 0 },
24. Point { x: 1, y: 3 });

26. println!("Success!")
27. }

完全限定语法

在 Rust 中,两个不同特征的方法完全可以同名,且你可以为同一个类型同时实现这两个特征。这种情况下,就出现了一个问题:该如何调用这两个特征上定义的同名方法。为了解决这个问题,我们需要使用完全限定语法( Fully Qualified Syntax )。

示例

1. trait UsernameWidget {
2. fn get(&self) -> String;
3. }

5. trait AgeWidget {
6. fn get(&self) -> u8;
7. }

9. struct Form {
10. username: String,
11. age: u8,
12. }

14. impl UsernameWidget for Form {
15. fn get(&self) -> String {
16. self.username.clone()
17. }
18. }

20. impl AgeWidget for Form {
21. fn get(&self) -> u8 {
22. self.age
23. }
24. }

26. fn main() {
27. let form = Form{
28. username: "rustacean".to_owned(),
29. age: 28,
30. };

32. // 如果你反注释下面一行代码,将看到一个错误: Fully Qualified Syntax
33. // 毕竟,这里有好几个同名的 `get` 方法
34. //
35. // println!("{}", form.get());

37. let username = UsernameWidget::get(&form);
38. assert_eq!("rustacean".to_owned(), username);
39. let age = AgeWidget::get(&form); // 你还可以使用以下语法 `<Form as AgeWidget>::get`
40. assert_eq!(28, age);

42. println!("Success!")
43. }

练习题

  1. 🌟🌟
1. trait Pilot {
2. fn fly(&self) -> String;
3. }

5. trait Wizard {
6. fn fly(&self) -> String;
7. }

9. struct Human;

11. impl Pilot for Human {
12. fn fly(&self) -> String {
13. String::from("This is your captain speaking.")
14. }
15. }

17. impl Wizard for Human {
18. fn fly(&self) -> String {
19. String::from("Up!")
20. }
21. }

23. impl Human {
24. fn fly(&self) -> String {
25. String::from("*waving arms furiously*")
26. }
27. }

29. fn main() {
30. let person = Human;

32. assert_eq!(__, "This is your captain speaking.");
33. assert_eq!(__, "Up!");

35. assert_eq!(__, "*waving arms furiously*");

37. println!("Success!")
38. }

Supertraits

有些时候我们希望在特征上实现类似继承的特性,例如让一个特征 A 使用另一个特征 B 的功能。这种情况下,一个类型要实现特征 A 首先要实现特征 B, 特征 B 就被称为 supertrait

  1. 🌟🌟🌟
2. trait Person {
3. fn name(&self) -> String;
4. }

6. // Person 是 Student 的 supertrait .
7. // 实现 Student 需要同时实现 Person.
8. trait Student: Person {
9. fn university(&self) -> String;
10. }

12. trait Programmer {
13. fn fav_language(&self) -> String;
14. }

16. // CompSciStudent (computer science student) 是 Programmer
17. // 和 Student 的 subtrait. 实现 CompSciStudent 需要先实现这两个 supertraits.
18. trait CompSciStudent: Programmer + Student {
19. fn git_username(&self) -> String;
20. }

22. fn comp_sci_student_greeting(student: &dyn CompSciStudent) -> String {
23. format!(
24. "My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
25. student.name(),
26. student.university(),
27. student.fav_language(),
28. student.git_username()
29. )
30. }

32. struct CSStudent {
33. name: String,
34. university: String,
35. fav_language: String,
36. git_username: String
37. }

39. // 为 CSStudent 实现所需的特征
40. impl ...

42. fn main() {
43. let student = CSStudent {
44. name: "Sunfei".to_string(),
45. university: "XXX".to_string(),
46. fav_language: "Rust".to_string(),
47. git_username: "sunface".to_string()
48. };

50. // 填空
51. println!("{}", comp_sci_student_greeting(__));
52. }

孤儿原则

关于孤儿原则的详细介绍请参见特征定义与实现的位置孤儿规则 和 在外部类型上实现外部特征。

  1. 🌟🌟
1. use std::fmt;

3. // 定义一个 newtype `Pretty`

5. impl fmt::Display for Pretty {
6. fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
7. write!(f, "\"{}\"", self.0.clone() + ", world")
8. }
9. }

11. fn main() {
12. let w = Pretty("hello".to_string());
13. println!("w = {}", w);
14. }

你可以在这里找到答案(在 solutions 路径下)