Trait 基础

Trait 项(Item)

Trait 项是指包含于 trait 声明中的任意项。

Self

Self 总是指代实现类型。

  1. trait Trait {
  2. // returns emplementing type
  3. fn return_self() -> Self;
  4. }

函数(Function)

Trait 函数是指第一个参数不是 self 关键字的任意函数。

  1. trait Default {
  2. // function
  3. fn default()-> Self;
  4. }

Trait 函数可以通过 trait 或者实现类型的命名空间来调用。

  1. fn main(() {
  2. let zero: i32 = Default::default();
  3. let zero = i32::default();
  4. }

方法(Method)

Trait 方法是指,第一个参数使用了 self 关键字并且 self 关键字的类型是 self&self&mut self 之一。 self 的类型也可以被 BoxRcArcPin 来包装。

  1. trait Trait {
  2. fn take_self(self);
  3. //...
  4. // 去语法糖后为
  5. // fn take_self(self: Self);
  6. }
  7. // example from standard libarary
  8. trait ToString {
  9. fn to_string(&self) -> String;
  10. }

Trait 方法可以通过在实现类型上使用点(.)操作符来调用。

  1. fn main() {
  2. let five = 5.to_string();
  3. }

此外,trait 方法还可以像函数那样由 trait 或者实现类型通过命名空间来调用。

  1. fn main() {
  2. let five = ToString::to_string(&5);
  3. let fiv = i32::to_string(&5);
  4. }

关联类型(Associated Types)

Trait 可以有关联类型。当我们需要再函数签名中使用 Self 以外的某个类型,但是希望这个类型可以由实现着来选择而不是硬编码到 trait 声明中,这时关联类型就可以发挥作用了。

  1. trait Trait {
  2. type AssociatedType;
  3. fn func(arg: Self::AssociatedType);
  4. }
  5. struct SomeType;
  6. struct OtherType;
  7. // any type implementing Trait can
  8. // choose the type of AssociatedType
  9. impl Trait for SomeType {
  10. type AssociatedType = i8; // chooses i8
  11. fn func(arg: Self::AssociatedType) {}
  12. }
  13. impl Trait for OtherType {
  14. type AssociatedType = u8; // chooses u8
  15. fn func(arg: Self::AssociatedType) {}
  16. }
  17. fn main() {
  18. SomeType::func(-1_i8); // can only call func with i8 on SomeType
  19. OtherType::func(1_u8); // can only call func with u8 on OtherType
  20. }

泛型参数(Generic Parameters)

“泛型参数”繁殖泛型类型参数(gereric type parameters)、泛型生命周期参数(generic lifetime parameters)、以及泛型常量参数(generic const parameters)。因为这些说起来比较拗口,所以人们通常把它们简称为“泛型类型(generic type)”、“生命周期(lifetime)”和“泛型常量(generic const)”。由于我们将要讨论的所有标准库 trait 中国都没有使用泛型常量,所以它们不再本文的讨论范围之内。

我们可以使用参数来对比一个 triat 生命进行泛化(generalize)。

  1. // trait declaration generalized with lifetime & type parameters
  2. trait Trait<'a, T> {
  3. // signature uses generic type
  4. fn func1(arg: T);
  5. // signature uses lifetime
  6. fn func2(arg: &'a i32);
  7. // signature uses generic type & lifetime
  8. fn func3(arg: &'a T);
  9. }
  10. struct SomeType;
  11. impl<'a> Trait<'a, i8> for SomeType {
  12. fn func1(arg: i8) {}
  13. fn func2(arg: &'a i32) {}
  14. fn func3(arg: &'a i8) {}
  15. }
  16. impl<'b> Trait<'b, u8> for SomeType {
  17. fn func1(arg: u8){}
  18. fn func2(arg: &'b i32){}
  19. fn func3(arg: &'b u8){}
  20. }

泛型可以具有默认值,最常用的默认值是 Self ,但是任何类型都可以作为默认值。

  1. // make T = Self by default
  2. trait Trait<T = Self> {
  3. fn func(t: T) {}
  4. }
  5. // any type can be used as the default
  6. trait Trait2<T = i32> {
  7. fn func2(t: T) {}
  8. }
  9. struct SomeType;
  10. //omitting the generic type will cause the impl to
  11. // use the default value, which is Self here
  12. impl Trait for SomeType {
  13. fn func(t: SomeType) {}
  14. }
  15. // default value here is i32
  16. impl Trait2 for SomeType {
  17. fn func2(t: i32) {}
  18. }
  19. // the default is overridable as we'd expect
  20. impl Trait<String> for SomeType {
  21. fn func(t: String) {}
  22. }

上面是对 trait 进行参数化,还可以对单个函数和方法进行参数化。

  1. trait Trait {
  2. fn func<'a, T>(t: &'a T);
  3. }

泛型类型 VS 关联类型

泛型类型和关联类型都把在 trait 的函数和方法使用哪种具体类型的决定权交给了实现者。
通常的经验法则是:

  • 当每个类型只应该有 trait 的一个实现时,使用关联类型;
  • 当每个类型可能会有 trait 的多个实现时,使用泛型类型;

一个普通的函数相加的示例:

  1. trait Add {
  2. type Rhs;
  3. type Output;
  4. fn add(self, rhs: Self::Rhs) -> Self::Output;
  5. }
  6. struct Point {
  7. x: i32,
  8. y: i32,
  9. }
  10. impl Add for Point {
  11. type Ths = Point;
  12. type Output = Point;'
  13. fn add(self, rhs: Point) -> Point{
  14. Point {
  15. x: self.x + rhs.x,
  16. x: self,y + rhs.y,
  17. }
  18. }
  19. }
  20. fn main() {
  21. let p1 = Point {x: 1, y: 1};
  22. let p2 = Point {x: 2, y: 2};
  23. let ps = p1.add(p2);
  24. assert_eq!(p3.x, 3);
  25. assert_eq!(p3.y, 3);
  26. }

增加功能:把单个的数分别加到 Point 的 x 和 y。
当再次实现 impl Add for Point 就会出现错误,属于重复声明实现。所以这个时候必须将关联类型重构为泛型类型,这样就可以根据 Rhs 不同的类型参数来为 Point 实现 trait 多次。

  1. trait Add<Rhs> {
  2. type Output;
  3. fn add(self, rhs: Rhs) -> Self::Output;
  4. }
  5. struct Point {
  6. x: i32,
  7. y: i32,
  8. }
  9. impl Add<Point> for Point {
  10. type Output = Self;
  11. fn add(self, rhs: Point) -> Self::Output {
  12. Self {
  13. x: self.x + rhs.x,
  14. x: self.y + rhs.y,
  15. }
  16. }
  17. }
  18. impl Add<i32> for Point {
  19. type Output = Self;
  20. fn add(self, rhs: i32) -> Self::Output {
  21. Self {
  22. x: self.x + rhs,
  23. x: self.y + rhs,
  24. }
  25. }
  26. }
  27. fn main() {
  28. let p1 = Point { x: 1, y: 1 };
  29. let p2 = Point { x: 2, y: 2 };
  30. let p3 = p1.add(p2);
  31. assert_eq!(p3.x, 3);
  32. assert_eq!(p3.y, 3);
  33. let p1 = Point { x: 1, y: 1 };
  34. let int2 = 2;
  35. let p3 = p1.add(int2); // ✅
  36. assert_eq!(p3.x, 3);
  37. assert_eq!(p3.y, 3);
  38. }

增加新功能:增加一个 Line 的新类型,两个 Point 相加之后应该产生一个 Line 。需要将 Output 从关联类型重构为泛型类型来实现这个需求。

  1. trait Add<Rhs, Output> {
  2. fn add(self, rhs: Rhs) -> Output;
  3. }
  4. struct Point {
  5. x: i32,
  6. y: i32,
  7. }
  8. impl Add<Point, Point> for Point {
  9. fn add(self, rhs: Point) -> Point {
  10. Self {
  11. x: self.x + rhs.x,
  12. y: self.y + rhs.y,
  13. }
  14. }
  15. }
  16. impl Add<i32, Point> for Point {
  17. fn add(self, rhs: i32) -> Point {
  18. Point {
  19. x: self.x + rhs,
  20. y: self.y + rhs,
  21. }
  22. }
  23. }
  24. struct Line {
  25. start: Point,
  26. end: Point,
  27. }
  28. impl Add<Point, Line> for Point {
  29. fn add(self, rhs: Point) -> Line {
  30. Line {
  31. start: self,
  32. end: rhs,
  33. }
  34. }
  35. }

所以具体的 Add trait 需要程序的需求!

作用域

只有当 trait 在作用域之中时,trait 项才能被使用。
ReadWrite 的 trait 并不在标准库的 prelude 中。

  1. use std::fs::File;
  2. use std::io;
  3. fn main() -> Result<(), io::Error> {
  4. let mut file = File::open("Cargo.toml")?;
  5. let mut buffer = String::new();
  6. // ❌ read_to_string not found in File
  7. file.read_to_string(&mut buffer)?;
  8. }

read_to_string 声明与 std::io::Read 中并且被 std::fs::File 结构体实现,但是要想调用它, std::io::Read 必须在当前作用域中。

  1. use std::io;
  2. use std::fs::File;
  3. use std::io::Read;
  4. fn main() -> Result<(), io::Error> {
  5. let mut file = File::open("Cargo.toml")?;
  6. let mut buffer = String::new();
  7. file.read_to_string(&mut buffer)?;
  8. println!("{}", buffer);
  9. Ok(())
  10. }

标准库预置(The standard library prelude)是标准库中的一个模块,也就是说,std::prelude::v1,它在每个其他模块的顶部被自动导入,即use std::prelude::v1::*。这样的话,下面这些 trait 就总会在作用域中,我们不需要自己显式地导入它们,因为它们是预置的一部分。

  • AsMut
  • AsRef
  • Clone
  • Copy
  • Default
  • Drop
  • Eq
  • Fn
  • FnMut
  • FnOnce
  • From
  • Into
  • ToOwned
  • IntoIterator
  • Iterator
  • PartialEq
  • PartialOrd
  • Send
  • Sized
  • Sync
  • ToString
  • Ord

    派生宏(Derive Macros)

    标准库导出了一小部分派生宏,这些派生宏可以让我们便捷地在一个类型上实现 trait,前提是该类型的所有成员都实现了这个 trait。派生宏以它们实现的 trait 来命名。

  • Clone

  • Copy
  • Debug
  • Default
  • Eq
  • Hash
  • Ord
  • PartialEq
  • PartialOrd

使用示例:

  1. #[derive(Copy, Clone)]
  2. struct SomeType;

注意:派生宏也是过程宏(procedural macros),它们可以被用来做任何事情,没有强制规定它们必须要实现一个 trait,或者它们只能在所有成员都实现 trait 的情况下才能工作,这些只是标准库中派生宏所遵循的惯例。

默认实现(Default Impls)

Trait 可以为它们的函数和方法提供默认实现。

  1. impl Trait {
  2. fn method(&self) {
  3. println!("default impl");
  4. }
  5. }

如果 trait 中的某些方法是完全通过 trait 的另一些方法来实现的,这就非常方便了。

  1. trait Greet {
  2. fn greet(&self, name: &str) -> String;
  3. fn greet_loudly(&self, name: &str) -> String {
  4. self.greet(name) + "!"
  5. }
  6. }
  7. struct Hello;
  8. struct Hola;
  9. impl Greet for Hello {
  10. fn greet(&self, name: &str) -> String {
  11. format!("Hello {}", name)
  12. }
  13. // use default impl for greet_loudly
  14. }
  15. impl Greet for Hola {
  16. fn greet(&self, name: &str) -> String {
  17. format!("Hola {}", name)
  18. }
  19. // override default impl
  20. fn greet_loudly(&self, name: &str) -> String {
  21. let mut greeting = self.greet(name);
  22. greeting.insert_str(0, "¡");
  23. greeting + "!"
  24. }
  25. }
  26. fn main() {
  27. println!("{}", Hello.greet("John")); // prints "Hello John"
  28. println!("{}", Hello.greet_loudly("John")); // prints "Hello John!"
  29. println!("{}", Hola.greet("John")); // prints "Hola John"
  30. println!("{}", Hola.greet_loudly("John")); // prints "¡Hola John!"
  31. }

泛型覆盖实现(Generic Blanket Impls)

泛型覆盖实现是一种在泛型类型而不是具体类型上的实现。为整数类型实现一个 is_even 方法开始。

  1. trait Even {
  2. fn is_even(self) -> bool {}
  3. }
  4. impl Even for i8 {
  5. fn is_even(self) -> bool {
  6. self % 2_i8 == 0_i8
  7. }
  8. }
  9. impl Even for u8 {
  10. fn is_even(self) -> bool {
  11. self % 2_u8 == 0_u8
  12. }
  13. }
  14. impl Even for i16 {
  15. fn is_even(self) -> bool {
  16. self % 2_i16 = 0_i16
  17. }
  18. }

通过使用泛型来覆盖实现重复逻辑:

  1. use std::fmt::Debug;
  2. use std::convert::TryInto;
  3. use std::ops::Rem;
  4. trait Even {
  5. fn is_even(self) -> bool;
  6. }
  7. // generic blanket impl
  8. impl<T> Even for T
  9. where
  10. T: Rem<Output = T> + ParticalEq<T> + Sized,
  11. u8: TryInto<T>,
  12. <u8 as TryInto<T>>::Error: Debug,
  13. {
  14. fn is_even(self) -> bool {
  15. self % 2.try_into().unwrap() == 0.try_into().unwrap()
  16. }
  17. }
  18. // 不可以再为其他类型实现,下面的实现时错误的
  19. // impl Even for u8{}

trait 一致性是指,对于任意给定的类型,最多存在某一 trait 的一个实现。Rust 用来强制执行特质一致性的规则。

Subtraits & Supertraits

subtrait中的sub指的是子集(subset),supertrait中的super指的是超集(superset)。如果我们有下面这个 trait 声明:

  1. trait Subtrait: Supertrait {}

所有实现了Subtrait的类型是所有实现了Supertrait的类型的子集,
上面的代码是一种语法糖,展开为:

  1. trait Substrait where Self: Supertriat {}

这是一个微妙而重要的区别,要明白约束在Self上,也就是实现Subtrait的类型而非Subtrait自身。后者也没有意义,因为 trait 约束只能作用于能够实现 trait 的具体类型,trait 本身不能实现其他的 trait:

  1. trait Supertrait {
  2. fn method(&self) {
  3. println!("in supertrait");
  4. }
  5. }
  6. trait Subtrait: Supertrait {
  7. // this looks like it might impl or
  8. // override Supertrait::method but it does not
  9. fn method(&self) {
  10. println!("in subtrait")
  11. }
  12. }
  13. struct SomeType;
  14. impl Supertrait for SomeType {}
  15. impl Subtrait for SomeType {}
  16. fn main() {
  17. SomeType.method(); // ❌ ambiguous method call
  18. <SomeType as Supertrait>::method(&st); // ✅ prints "in supertrait"
  19. <SomeType as Subtrait>::method(&st); // ✅ prints "in subtrait"
  20. }

对于一个类型如何同时实现一个 subtrait 和一个 supertrait,也没有明确的规则。

  1. trait Supertrait {
  2. fn super_method(&mut self);
  3. }
  4. trait Subtrait: Supertrait {
  5. fn sub_method(&mut self);
  6. }
  7. struct CallSuperFromSub;
  8. impl Supertrait for CallSuperFromSub {
  9. fn super_method(&mut self) {
  10. println!("in super");
  11. }
  12. }
  13. impl Subtrait for CallSuperFromSub {
  14. fn sub_method(&mut self) {
  15. println!("in sub");
  16. self.super_method();
  17. }
  18. }
  19. struct CallSubFromSuper;
  20. impl Supertrait for CallSubFromSuper {
  21. fn super_method(&mut self) {
  22. println!("in super");
  23. self.sub_method();
  24. }
  25. }
  26. impl Subtrait for CallSubFromSuper {
  27. fn sub_method(&mut self) {
  28. println!("in sub");
  29. }
  30. }
  31. struct CallEachOther(bool);
  32. impl Supertrait for CallEachOther {
  33. fn super_method(&mut self) {
  34. println!("in super");
  35. if self.0 {
  36. self.0 = false;
  37. self.sub_method();
  38. }
  39. }
  40. }
  41. impl Subtrait for CallEachOther {
  42. fn sub_method(&mut self) {
  43. println!("in sub");
  44. if self.0 {
  45. self.0 = false;
  46. self.super_method();
  47. }
  48. }
  49. }
  50. fn main() {
  51. CallSuperFromSub.super_method(); // prints "in super"
  52. CallSuperFromSub.sub_method(); // prints "in sub", "in super"
  53. CallSubFromSuper.super_method(); // prints "in super", "in sub"
  54. CallSubFromSuper.sub_method(); // prints "in sub"
  55. CallEachOther(true).super_method(); // prints "in super", "in sub"
  56. CallEachOther(true).sub_method(); // prints "in sub", "in super"
  57. }

subtrait 和 supertrait 之间可以是很复杂的关系。在介绍能够将这些复杂性进行整洁封装的心智模型之前,让我们快速回顾并建立我们用来理解泛型类型上的 trait 约束的心智模型。

  1. fn function<T: Clone>(t: T) {
  2. // impl
  3. }

合理的猜测, t.clone() 会在某个时候被调用,因为当一个泛型类型被一个 trait 所约束时,意味着它对 trait 有依赖性。泛型与 trait 约束之间关系的心智模型是一个简单而直观的模型:泛型依赖于 trait 约束。

Copy 的trait 声明:

  1. trait Copy: Clone {}

但是 Copy 完全不依赖于 Clone,之前的模型在这里没有帮助。个人认为,理解 subtrait 与 supertrait 最为简洁优雅的心智模型是:subtrait 细化(refine)了他们的supertrait。
“细化(Refinement)”刻意保持一定的模糊性,因为它们在不同的上下文环境中会有不同的含义:

  • subtrait 可能会使得 supertrait 的方法实现更为具体,快速,占用更少的内存,例如: Copy: Clone
  • subtrait 可能会对 supertrait 的方法实现增加额外的保证,例如: Eq: PartialEqOrd: PartialOrdExactSizeIterator: Iterator
  • subtrait 可能会使得 supertrait 的方法更为灵活和易于调用,例如: FnMut: FnOnceFn: FnMut
  • subtrait 可能会扩展 supertrait 并添加新的方法,例如: DoubleEndedInterator: IteratorExactSizeIterator: Iterator

    Trait 对象

    泛型给我提供了编译器多态,而 trait 对象给我提供了运行时多态。我们可以使用 trait 对象来让函数在运行时动态地返回不同的类型。
    1. fn example(condition: bool, vec: Vec<i32>) -> Box<dyn Iterator<Item = i32>> {
    2. let iter = vec.into_iter();
    3. if condition {
    4. // Has type:
    5. // Box<Map<IntoIter<i32>, Fn(i32) -> i32>>
    6. // But is cast to:
    7. // Box<dyn Iterator<Item = i32>>
    8. Box::new(iter.map(|n| n * 2))
    9. } else {
    10. // Has type:
    11. // Box<Filter<IntoInter<i32>, Fn(&i32) -> bool>>
    12. // But is cast to:
    13. // Box<dyn Iterator<Item = i32>>
    14. Box::new(iter.filter<|&n| n >= 2))
    15. }
    16. }
    Trait 对象还允许我们在集合中存储多种类型: ```rust use std::f64::consts::PI;

struct Circle { radius: f64, }

struct Square { side: f64, }

trait Shape { fn area(&self) -> f64; }

impl Shape for Circle { fn area(&self) -> f64 { PI self.radius self.radius } }

impl Shape for Square { fn area(&self) -> f64 { self.side * self.side } }

fn get_total_area(shapes: Vec>) -> f64 { shapes.into_iter().map(|s| s.area()).sum() }

fn example() { let shapes: Vec> = vec![ Box::new(Circle {radius: 1.0}), // Box cast to Box Box::new(Square {size: 1.0}), // Box cast to Box, ];

  1. assert_eq!(PI + 1.0, get_total_area(shapes));

}

  1. trait对象是没有大小的,所以它们必须总是在一个指针后面。我们可以根据类型中 `dyn` 关键字的存在来区分具体类型和 trait 对象在类型级别上的区别。
  2. ```rust
  3. struct Struct;
  4. trait Trait {}
  5. // regular struct
  6. &Struct
  7. Box<Struct>
  8. Rc<Struct>
  9. Arc<Struct>
  10. // trait objects
  11. &dyn Trait
  12. Box<dyn Trait>
  13. Rc<dyn Trait>
  14. Arc<dyn Trait>

不是所有的 trait 都可以被转换成 trait 对象。当且仅当一个 trait 满足下面这些要求时,它才是对象安全的(obect-safe):

  • trait 不要求 Self:Sized
  • trait 的所有方法都是对象安全的

当一个 trait 方法满足下面的要求时,该方法是对象安全的:

  • 方法要求 Self:Sized 或者
  • 方法在其接受者位置仅使用一个 Self 类型

理解为什么要求是这样的,与本文的其余部分无关,但如果你仍然好奇,可以阅读Sizeness in Rust

标记 Trait(Market Traits)

标记 trait 是不含 trait 项的 trait。它们的工作把实现类型“标记(mark)”为具有某种属性,否则就没有办法在类型系统中去表示。

  1. // Impling PartialEq for a tye promises
  2. // that eqality for the type has these properties:
  3. // - symmetry: a == b implies b == a,and
  4. // - transitivity: a == b && b == a implies a == c // 传递性
  5. // But does not promise this property:
  6. // - reflexivity: a == a // 自反性
  7. trait PartialEq {
  8. fn eq(&self, other: &Self) -> bool;
  9. }
  10. // Eq has no trait items! The eq method is already
  11. // declared by PartialEq, but "impling" Eq
  12. // for a type promises this additional equality property:
  13. // - reflexivity: a == a
  14. trait Eq: PartialEq {}
  15. // f64 impls PartialEq but not Eq because NaN != NaN
  16. // i32 impls PartialEq & EQ because there's no NaNs :)

自动 Trait(Auto Trait)

自动 Trait 是指如果一个类型的所有成员都实现了该 trait,该类型就会自动实现该 trait。“成员(member)”的含义取决于类型,例如:结构体的字段、枚举的变量、数组的元素、元组的项,等等。

所有的自动 trait 都是标记 trait,但不是所有的标记 trait 都是自动 trait。自动 trait 必须是标记 trait,所以编译器可以为它们提供一个自动的默认实现,如果它们有任何 trait 项,这就不可能实现了。
自动 trait 的例子。

  1. // implemented for types which are safe to send between threads
  2. unsafe auto trait Send {}
  3. // implemented for types whose references are safe to send between threads
  4. unsafe auto trait Sync {}

不安全 Trait(Unsafe Trait)

trait 可以被标记为 unsafe,以表明实现该 trait 可能需要 unsafe 代码。SendSync都被标记为 unsafe,因为如果它们不是自动实现的类型,就意味着它必须包含一些非Send或非Sync的成员,如果我们想手动标记类型为SendSync,作为实现者我们必须格外小心,确保没有数据竞争。

参考

https://mp.weixin.qq.com/s/kY9q7_mv3sfGq8kPM0_meg