让我们再次审视我们的 Ticket
类型:
#![allow(unused)] fn main() { pub struct Ticket { title: String, description: String, status: String, } }
迄今为止,我们的所有测试都是使用 Ticket
的字段进行断言的。
#![allow(unused)] fn main() { assert_eq!(ticket.title(), "一个新的标题"); }
如果我们想直接比较两个 Ticket
实例会怎样呢?
#![allow(unused)] fn main() { let ticket1 = Ticket::new(/* ... */); let ticket2 = Ticket::new(/* ... */); ticket1 == ticket2 }
编译器会阻止我们:
error[E0369]: binary operation `==` cannot be applied to type `Ticket`
--> src/main.rs:18:13
|
18 | ticket1 == ticket2
| ------- ^^ ------- Ticket
| |
| Ticket
|
note: an implementation of `PartialEq` might be missing for `Ticket`
Ticket
是一个新类型。开箱即用,它没有任何行为附着于其上**。Rust 不会神奇地推断如何比较两个 Ticket
实例,仅仅因为它们包含了 String
s。
不过,Rust 编译器正把我们推向正确的方向:它提示我们可能缺少了 PartialEq
的实现。PartialEq
是一个特性!
特性是什么?
特性是Rust定义接口的方式。
特性定义了一组类型必须实现的方法,以满足特性的契约。
定义特性
特性定义的语法如下:
#![allow(unused)] fn main() { trait <TraitName> { fn <method_name>(<parameters>) -> <return_type>; } }
例如,我们可能会定义一个名为 MaybeZero
的特性,要求其实现者定义一个 is_zero
方法:
#![allow(unused)] fn main() { trait MaybeZero { fn is_zero(self) -> bool; } }
实现特性
为类型实现特性时,我们使用 impl
关键字,就像我们为常规1方法那样,但语法略有不同:
#![allow(unused)] fn main() { impl <TraitName> for <TypeName> { fn <method_name>(<parameters>) -> <return_type> { // Method body } } }
例如,为自定义数字类型 WrappingU32
实现 MaybeZero
特性:
#![allow(unused)] fn main() { pub struct WrappingU32 { inner: u32, } impl MaybeZero for WrappingU32 { fn is_zero(self) -> bool { self.inner == 0 } } }
调用特性方法
调用特性方法时,我们使用.
操作符,就像调用常规方法一样:
#![allow(unused)] fn main() { let x = WrappingU32 { inner: 5 }; assert!(!x.is_zero()); }
要调用特性方法,两件事必须为真:
- 类型必须实现了该特性。
- 特性必须在作用域内。
为满足后者,你可能需要添加一个 use
语句来引入特性:
#![allow(unused)] fn main() { use crate::MaybeZero; }
如果:
- 特性在调用发生的同一模块中定义。
- 特性定义在标准库的预置中。预置是一组自动导入到每个Rust程序中的特性和类型。这就像是在每个Rust模块开头添加了
use std::prelude::*;
。
你可以在Rust文档中找到预置中特性和类型的列表:https://doc.rust-lang.org/std/prelude/index.html。
参考资料
- 本节练习位于
exercises/04_traits/01_trait
1
直接定义在一个类型上,而不使用特性的方法,也称为固有方法。