0x00 开篇
看到这里,我想大家应该对 trait 都有了初步的了解了。本篇文章将向大家介绍下在 Rust 标准库中常用和常见的一些 trait。
0x01 derive
在介绍常用 trait 前,我们先了解下 Derive, Derive 我们常翻译为“派生”。在 Rust 中,有 #[derive]
这样一个属性,通过这个属性,编译器能够提供某些 trait 的基本实现。当然如果在实际开发中需要更复杂的行为,这些 trait 也可以手动实现。
0x02 Debug
源码:
#[doc(alias = "{:?}")] #[rustc_diagnostic_item = "Debug"] #[rustc_trivial_field_reads] pub trait Debug { fn fmt(&self, f: &mut Formatter<'_>) -> Result; }
Debug 是可以与 derive
属性一起使用的。官方提供了默认 {:?}
的实现。在结构体的文章中,我们曾用过这个属性。相对于结构体等一些类型的实例,我们是无法直接通过 println("{:?}")
或者 dbg()
打印它们的。这时我们可以为其类型加上 #[derive(Debug)]
属性。
示例代码:
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } fn main() { // 1、Debug let rec1 = Rectangle { width: 3, height: 5, }; println!("{:?}", rec1); let rec2 = Rectangle { width: 6, height: 4, }; dbg!(rec2); }
如果我们在打印时,不加 #[derive(Debug)]
属性。则编译器会提示错误the trait Debug is not implemented for Rectangle
,并且建议添加 #[derive(Debug)]
。
可以说 Debug trait 方便了我们的调试。
0x03 Eq 和 PartialEq
Eq
是 Equal 的缩写,即这两个是判断是否相等的 trait
。他们同样可以与 derive
连用。
源码:
pub trait PartialEq<Rhs: ?Sized = Self> { fn eq(&self, other: &Rhs) -> bool; fn ne(&self, other: &Rhs) -> bool { !self.eq(other) } } pub trait Eq: PartialEq<Self> { fn assert_receiver_is_total_eq(&self) {} }
如果我们想比较某个类型的两个值 a 和 b是否相等,那么我们就必须为类型实现 PartialEq Trait
。可以看到 Eq
和 PartialEq
是父子关系。要实现 Eq
必须在实现 PartialEq
的基础上实现。 Eq
和 PartialEq
来自于抽象代数中的等价关系和局部等价关系。两个都满足了对称性(即 a == b
可以推出 b == a
)和传递性(即a == b
和 b == c
可以推出 a == c
)。Eq
还需要满足自反性(即 a == a)。在 Rust 中,浮点数类型两个 NAN
是不相等的。Rust 只为其实现了PartialEq
。下面是官方源码(来自cmp.rs
):
partial_eq_impl! { bool char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 f32 f64 } eq_impl! { () bool char usize u8 u16 u32 u64 u128 isize i8 i16 i32 i64 i128 }
我们自己实现 PartialEq
来判断两个三角形是否相似。
// 三角形 struct Triangle { a: f64, b: f64, c: f64, } // 自己实现 PartialEq // 判断三角形相似 impl PartialEq for Triangle { fn eq(&self, other: &Self) -> bool { let x = self.a / other.a; let y = self.b / other.b; let z = self.c / other.c; return x == y && x == z && y == z; } fn ne(&self, other: &Self) -> bool { return !eq(self, other); } } fn main() { // 相似三角形 let tri1 = Triangle { a: 3.0, b: 4.0, c: 5.0 }; let tri2 = Triangle { a: 6.0, b: 8.0, c: 10.0 }; println!("tri1 和 tri2 相似 ? {}", tri1 == tri2); // 运行结果 // tri1 和 tri2 相似 ? true }
当然也可以通过 derive 来默认实现结构体的比较。
#[derive(PartialEq)] struct Rectangle { width: u32, height: u32, } fn main() { let rec1 = Rectangle { width: 3, height: 5, }; let rec2 = Rectangle { width: 3, height: 5, }; println!("rec1 == rec2 ? {}", rec1 == rec2); // 运行结果 // rec1 == rec2 ? true }
0x04 Ord 和 PartialOrd
Ord
是 Order 的缩写,即这两个是判断是全序关系的 trait
。他们同样可以与 derive
连用。全序关系是指集合内的任何一对元素都是相互可以比较的。Ord
和 PartialOrd
的使用方法同 Eq
和 PartialEq
类似。但是有两个依赖要求,PartialOrd
必须要求类型实现 PartialEq
和 Ord
必须要求类型实现 PartialOrd
和 Eq
。 Ord
中还提供了 max 和 min 方法,更加方便进行比较。
源码:
pub trait PartialOrd<Rhs: ?Sized = Self>: PartialEq<Rhs> { fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>; fn lt(&self, other: &Rhs) -> bool { matches!(self.partial_cmp(other), Some(Less)) } fn le(&self, other: &Rhs) -> bool { !matches!(self.partial_cmp(other), None | Some(Greater)) } fn gt(&self, other: &Rhs) -> bool { matches!(self.partial_cmp(other), Some(Greater)) } fn ge(&self, other: &Rhs) -> bool { matches!(self.partial_cmp(other), Some(Greater | Equal)) } } pub trait Ord: Eq + PartialOrd<Self> { fn cmp(&self, other: &Self) -> Ordering; fn max(self, other: Self) -> Self where Self: Sized, { max_by(self, other, Ord::cmp) } fn min(self, other: Self) -> Self where Self: Sized, { min_by(self, other, Ord::cmp) } fn clamp(self, min: Self, max: Self) -> Self where Self: Sized, { assert!(min <= max); if self < min { min } else if self > max { max } else { self } } }
通过derive
为结构体类型添加可以比较的属性。示例代码:
#[derive(PartialEq)] #[derive(PartialOrd)] struct Rectangle { width: u32, height: u32, } fn main() { let rec1 = Rectangle { width: 1, height: 5, }; let rec2 = Rectangle { width: 3, height: 5, }; println!("rec1 > rec2 ? {}", rec1 > rec2); }
自定义 PartialOrd
的代码我就不贴了。 自定义 PartialOrd
时,需要实现 partial_cmp
方法。示例:有一种物体比较大小取决于价格,它的价格越低就越大。
// 货物 #[derive(PartialEq)] struct Good { price: f64, } impl PartialOrd for Good { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { // 要求价格低的反而大 return if self.price < other.price { Some(Ordering::Greater) } else if self.price > other.price { Some(Less) } else { Some(Ordering::Equal) }; } } fn main() { let good1 = Good { price: 1.0 }; let good2 = Good { price: 2.0 }; println!("good1 > good1 ? {}", good1 > good2); } // 运行结果 // good1 > good2 ? true
partial_cmp
方法返回值是 Option<Ordering>
类型,这里需要注意下(有关Option
类型相关的知识点请前往 Rust 中级教程 第1课)。Option
类型包含一个 Ordering
枚举。如果返回 Greater
表示当前值比相比较的另一个值大,Less
表示当前值比相比较的另一个值小,Equal
则表示相等。
Ordering
官方源码(来自cmp.rs
):
pub enum Ordering { /// An ordering where a compared value is less than another. #[stable(feature = "rust1", since = "1.0.0")] Less = -1, /// An ordering where a compared value is equal to another. #[stable(feature = "rust1", since = "1.0.0")] Equal = 0, /// An ordering where a compared value is greater than another. #[stable(feature = "rust1", since = "1.0.0")] Greater = 1, }
0x05 小结
本篇文章结合官方源码介绍一些比较常见且可以使用 derive
属性一起使用的 trait。由于篇幅有限,下一篇文章则还会继续了解一些常用的 trait
,以及使用derive
属性时编译器做了什么。另外,所有源码我也迁移到了 github 上面,在之后 github 和 gitee 的源码将同步更新。