• Traits
    • 泛型函数的 trait bound(Trait bounds on generic functions)
    • 泛型结构体的 trait bound(Trait bounds on generic structs)
    • 实现 trait 的规则(Rules for implementing traits)
    • 多 trait bound(Multiple trait bounds)
    • where 从句(Where clause)
    • 默认方法(Default methods)
    • 继承(Inheritance)
    • Deriving

    Traits

    traits.md


    commit 47f4de574df32c32b4b14081e5d6e90275b08ed3

    trait 是一个告诉 Rust 编译器一个类型必须提供哪些功能语言特性。

    你还记得impl关键字吗,曾用方法语法调用方法的那个?

    1. struct Circle {
    2. x: f64,
    3. y: f64,
    4. radius: f64,
    5. }
    6. impl Circle {
    7. fn area(&self) -> f64 {
    8. std::f64::consts::PI * (self.radius * self.radius)
    9. }
    10. }

    trait 也很类似,除了我们用函数标记来定义一个 trait,然后为结构体实现 trait。例如,我们为Circle实现HasArea trait:

    1. struct Circle {
    2. x: f64,
    3. y: f64,
    4. radius: f64,
    5. }
    6. trait HasArea {
    7. fn area(&self) -> f64;
    8. }
    9. impl HasArea for Circle {
    10. fn area(&self) -> f64 {
    11. std::f64::consts::PI * (self.radius * self.radius)
    12. }
    13. }

    如你所见,trait块与impl看起来很像,不过我们没有定义一个函数体,只是函数标记。当我们impl一个trait时,我们使用impl Trait for Item,而不是仅仅impl Item

    Self可以被用在类型标记中表示被作为参数传递的实现了这个 trait 的类型的一个实例。Self&Self&mut Self可以根据所需不同级别的所有权来使用。

    1. struct Circle {
    2. x: f64,
    3. y: f64,
    4. radius: f64,
    5. }
    6. trait HasArea {
    7. fn area(&self) -> f64;
    8. fn is_larger(&self, &Self) -> bool;
    9. }
    10. impl HasArea for Circle {
    11. fn area(&self) -> f64 {
    12. std::f64::consts::PI * (self.radius * self.radius)
    13. }
    14. fn is_larger(&self, other: &Self) -> bool {
    15. self.area() > other.area()
    16. }
    17. }

    泛型函数的 trait bound(Trait bounds on generic functions)

    trait 很有用是因为他们允许一个类型对它的行为提供特定的承诺。泛型函数可以显式的限制(或者叫 bound)它接受的类型。考虑这个函数,它并不能编译:

    1. fn print_area<T>(shape: T) {
    2. println!("This shape has an area of {}", shape.area());
    3. }

    Rust 抱怨道:

    1. error: no method named `area` found for type `T` in the current scope

    因为T可以是任何类型,我们不能确定它实现了area方法。不过我们可以在泛型T添加一个 trait bound,来确保它实现了对应方法:

    1. # trait HasArea {
    2. # fn area(&self) -> f64;
    3. # }
    4. fn print_area<T: HasArea>(shape: T) {
    5. println!("This shape has an area of {}", shape.area());
    6. }

    <T: HasArea>语法是指any type that implements the HasArea trait(任何实现了HasAreatrait的类型)。因为 trait 定义了函数类型标记,我们可以确定任何实现HasArea将会拥有一个.area()方法。

    这是一个扩展的例子演示它如何工作:

    1. trait HasArea {
    2. fn area(&self) -> f64;
    3. }
    4. struct Circle {
    5. x: f64,
    6. y: f64,
    7. radius: f64,
    8. }
    9. impl HasArea for Circle {
    10. fn area(&self) -> f64 {
    11. std::f64::consts::PI * (self.radius * self.radius)
    12. }
    13. }
    14. struct Square {
    15. x: f64,
    16. y: f64,
    17. side: f64,
    18. }
    19. impl HasArea for Square {
    20. fn area(&self) -> f64 {
    21. self.side * self.side
    22. }
    23. }
    24. fn print_area<T: HasArea>(shape: T) {
    25. println!("This shape has an area of {}", shape.area());
    26. }
    27. fn main() {
    28. let c = Circle {
    29. x: 0.0f64,
    30. y: 0.0f64,
    31. radius: 1.0f64,
    32. };
    33. let s = Square {
    34. x: 0.0f64,
    35. y: 0.0f64,
    36. side: 1.0f64,
    37. };
    38. print_area(c);
    39. print_area(s);
    40. }

    这个程序会输出:

    1. This shape has an area of 3.141593
    2. This shape has an area of 1

    如你所见,print_area现在是泛型的了,并且确保我们传递了正确的类型。如果我们传递了错误的类型:

    1. print_area(5);

    我们会得到一个编译时错误:

    1. error: the trait bound `_ : HasArea` is not satisfied [E0277]

    泛型结构体的 trait bound(Trait bounds on generic structs)

    泛型结构体也从 trait bound 中获益。所有你需要做的就是在你声明类型参数时附加上 bound。这里有一个新类型Rectangle<T>和它的操作is_square()

    1. struct Rectangle<T> {
    2. x: T,
    3. y: T,
    4. width: T,
    5. height: T,
    6. }
    7. impl<T: PartialEq> Rectangle<T> {
    8. fn is_square(&self) -> bool {
    9. self.width == self.height
    10. }
    11. }
    12. fn main() {
    13. let mut r = Rectangle {
    14. x: 0,
    15. y: 0,
    16. width: 47,
    17. height: 47,
    18. };
    19. assert!(r.is_square());
    20. r.height = 42;
    21. assert!(!r.is_square());
    22. }

    is_square()需要检查边是相等的,所以边必须是一个实现了core::cmp::PartialEq trait 的类型:

    1. impl<T: PartialEq> Rectangle<T> { ... }

    现在,一个长方形可以用任何可以比较相等的类型定义了。

    这里我们定义了一个新的接受任何精度数字的Rectangle结构体——讲道理,很多类型——只要他们能够比较大小。我们可以对HasArea结构体,SquareCircle做同样的事吗?可以,不过他们需要乘法,而要处理它我们需要了解运算符 trait更多。

    实现 trait 的规则(Rules for implementing traits)

    目前为止,我们只在结构体上添加 trait 实现,不过你可以为任何类型实现一个 trait,比如f32

    1. trait ApproxEqual {
    2. fn approx_equal(&self, other: &Self) -> bool;
    3. }
    4. impl ApproxEqual for f32 {
    5. fn approx_equal(&self, other: &Self) -> bool {
    6. // Appropriate for `self` and `other` being close to 1.0.
    7. (self - other).abs() <= ::std::f32::EPSILON
    8. }
    9. }
    10. println!("{}", 1.0.approx_equal(&1.00000001));

    这看起来有点像狂野西部(Wild West),不过这还有两个限制来避免情况失去控制。第一是如果 trait 并不定义在你的作用域,它并不能实现。这是个例子:为了进行文件I/O,标准库提供了一个Writetrait来为File增加额外的功能。默认,File并不会有这个方法:

    1. let mut f = std::fs::File::create("foo.txt").ok().expect("Couldn’t create foo.txt");
    2. let buf = b"whatever"; // buf: &[u8; 8], a byte string literal.
    3. let result = f.write(buf);
    4. # result.unwrap(); // Ignore the error.

    这里是错误:

    1. error: type `std::fs::File` does not implement any method in scope named `write`
    2. let result = f.write(buf);
    3. ^~~~~~~~~~

    我们需要先use这个Write trait:

    1. use std::io::Write;
    2. let mut f = std::fs::File::create("foo.txt").expect("Couldn’t create foo.txt");
    3. let buf = b"whatever";
    4. let result = f.write(buf);
    5. # result.unwrap(); // Ignore the error.

    这样就能无错误的编译了。

    这意味着即使有人做了像给i32增加函数这样的坏事,它也不会影响你,除非你use了那个 trait。

    这还有一个实现 trait 的限制。要么是 trait 要么是你写实现的类型必须由你定义。更准确的说,它们中的一个必须定义于你编写impl的相同的 crate 中。关于 Rust 的模块和包系统,详见crate 和模块。

    所以,我们可以为i32实现HasAreatrait,因为HasArea在我们的包装箱中。不过如果我们想为i32实现Floattrait,它是由 Rust 提供的,则无法做到,因为这个 trait 和类型都不在我们的包装箱中。

    关于 trait 的最后一点:带有 trait 限制的泛型函数是单态monomorphization)(mono:单一,morph:形式)的,所以它是静态分发statically dispatched)的。这是什么意思?查看trait 对象来了解更多细节。

    多 trait bound(Multiple trait bounds)

    你已经见过你可以用一个trait限定一个泛型类型参数:

    1. fn foo<T: Clone>(x: T) {
    2. x.clone();
    3. }

    如果你需要多于1个限定,可以使用+

    1. use std::fmt::Debug;
    2. fn foo<T: Clone + Debug>(x: T) {
    3. x.clone();
    4. println!("{:?}", x);
    5. }

    T现在需要实现CloneDebug

    where 从句(Where clause)

    编写只有少量泛型和trait的函数并不算太糟,不过当它们的数量增加,这个语法就看起来比较诡异了:

    1. use std::fmt::Debug;
    2. fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
    3. x.clone();
    4. y.clone();
    5. println!("{:?}", y);
    6. }

    函数的名字在最左边,而参数列表在最右边。限制写在中间。

    Rust有一个解决方案,它叫“where 从句”:

    1. use std::fmt::Debug;
    2. fn foo<T: Clone, K: Clone + Debug>(x: T, y: K) {
    3. x.clone();
    4. y.clone();
    5. println!("{:?}", y);
    6. }
    7. fn bar<T, K>(x: T, y: K) where T: Clone, K: Clone + Debug {
    8. x.clone();
    9. y.clone();
    10. println!("{:?}", y);
    11. }
    12. fn main() {
    13. foo("Hello", "world");
    14. bar("Hello", "world");
    15. }

    foo()使用我们刚才的语法,而bar()使用where从句。所有你所需要做的就是在定义参数时省略限制,然后在参数列表后加上一个where。对于很长的列表,你也可以加上空格:

    1. use std::fmt::Debug;
    2. fn bar<T, K>(x: T, y: K)
    3. where T: Clone,
    4. K: Clone + Debug {
    5. x.clone();
    6. y.clone();
    7. println!("{:?}", y);
    8. }

    这种灵活性可以使复杂情况变得简洁。

    where也比基本语法更强大。例如:

    1. trait ConvertTo<Output> {
    2. fn convert(&self) -> Output;
    3. }
    4. impl ConvertTo<i64> for i32 {
    5. fn convert(&self) -> i64 { *self as i64 }
    6. }
    7. // Can be called with T == i32.
    8. fn normal<T: ConvertTo<i64>>(x: &T) -> i64 {
    9. x.convert()
    10. }
    11. // Can be called with T == i64.
    12. fn inverse<T>(x: i32) -> T
    13. // This is using ConvertTo as if it were "ConvertTo<i64>".
    14. where i32: ConvertTo<T> {
    15. x.convert()
    16. }

    这突显出了where从句的额外的功能:它允许限制的左侧可以是任意类型(在这里是i32),而不仅仅是一个类型参数(比如T)。在这个例子中,i32必须实现ConvertTo<T>。不同于定义i32是什么(因为这是很明显的),这里的where从句限制了T

    默认方法(Default methods)

    默认方法可以增加在 trait 定义中,如果已经知道通常的实现会定义这个方法。例如,is_invalid()定义为与is_valid()相反:

    1. trait Foo {
    2. fn is_valid(&self) -> bool;
    3. fn is_invalid(&self) -> bool { !self.is_valid() }
    4. }

    Foo trait 的实现者需要实现is_valid(),不过并不需要实现is_invalid()。它会使用默认的行为。你也可以选择覆盖默认行为:

    1. # trait Foo {
    2. # fn is_valid(&self) -> bool;
    3. #
    4. # fn is_invalid(&self) -> bool { !self.is_valid() }
    5. # }
    6. struct UseDefault;
    7. impl Foo for UseDefault {
    8. fn is_valid(&self) -> bool {
    9. println!("Called UseDefault.is_valid.");
    10. true
    11. }
    12. }
    13. struct OverrideDefault;
    14. impl Foo for OverrideDefault {
    15. fn is_valid(&self) -> bool {
    16. println!("Called OverrideDefault.is_valid.");
    17. true
    18. }
    19. fn is_invalid(&self) -> bool {
    20. println!("Called OverrideDefault.is_invalid!");
    21. true // Overrides the expected value of is_invalid()
    22. }
    23. }
    24. let default = UseDefault;
    25. assert!(!default.is_invalid()); // Prints "Called UseDefault.is_valid."
    26. let over = OverrideDefault;
    27. assert!(over.is_invalid()); // Prints "Called OverrideDefault.is_valid."

    继承(Inheritance)

    有时,实现一个trait要求实现另一个trait:

    1. trait Foo {
    2. fn foo(&self);
    3. }
    4. trait FooBar : Foo {
    5. fn foobar(&self);
    6. }

    FooBar的实现也必须实现Foo,像这样:

    1. # trait Foo {
    2. # fn foo(&self);
    3. # }
    4. # trait FooBar : Foo {
    5. # fn foobar(&self);
    6. # }
    7. struct Baz;
    8. impl Foo for Baz {
    9. fn foo(&self) { println!("foo"); }
    10. }
    11. impl FooBar for Baz {
    12. fn foobar(&self) { println!("foobar"); }
    13. }

    如果我们忘了实现Foo,Rust 会告诉我们:

    1. error: the trait bound `main::Baz : main::Foo` is not satisfied [E0277]

    Deriving

    重复的实现像DebugDefault这样的 trait 会变得很无趣。为此,Rust 提供了一个属性来允许我们让 Rust 为我们自动实现 trait:

    1. #[derive(Debug)]
    2. struct Foo;
    3. fn main() {
    4. println!("{:?}", Foo);
    5. }

    然而,deriving 限制为一些特定的 trait:

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