Eq & PartialEq
-
符号:
==
、!=
-
区别:
Eq
相比于PartialEq
还需额外满足反身性,即a == a
。对于浮点类型,Rust 只实现了PartialEq
而不是
Eq
,原因就是NaN != NaN
。
PartialEq
可以直接使用 #[derive(PartialEq)]
派生宏交由编译器实现,对于 struct,Rust 会逐字段比较,对于 enum,会对 enum 中的数据进行比较。我们也可以自己实现该 trait。
struct Book {
name: String,
price: usize,
}
impl PartialEq for Book {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
Eq
实现 Eq
的前提是已经实现了 PartialEq
,如果已经实现了 PartialEq
,可以直接使用 #[derive(Eq)]
,但是当 struct 或者 enum 中存在浮点数时,我们需要手动实现该 trait。
#[derive(PartialEq)]
struct Book {
name: String,
price: f32
}
impl Eq for Book {} // 不用怀疑,就是这么简单~
Ord & PartialOrd
-
符号:
<
、⇐
、>
、>=
-
区别:
PartialOrd
不许满足连通性(a⇐b或a>=b
),只需满足反对称性(a ⇐ b 且 a >= b 可推出 a == b
)和传递性(a ⇐ b 且 b ⇐ c 可推出 a ⇐ c
)。
PartialOrd
Ord
& PartialOrd
均可通过 #[derive]
派生宏交由编译器自动实现,当使用派生宏实现后,将会基于 struct 的字段声明以字典序进行比较,遇到枚举中的数据也会以此类推。注意,实现 PartialOrd
要求该类型实现 PartialEq
。
struct Person {
name: String,
age: usize,
}
impl PartialEq for Person {
fn eq(&self, other: &Self) -> bool {
self.age == other.age
}
}
impl PartialOrd for Person {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.age.partial_cmp(&other.age)
}
}
你可能注意到了 partial_cmp
函数的返回值是一个Option
,之所以这么做是因为有时可能会与 NaN
作比较。
Ord
Ord
要求你的类型实现 PartialOrd
和 Eq
(因此 PartialEq
也需要被实现),实现 PartialEq
,PartialOrd
以及 Ord
时要特别注意彼此之间不能有冲突(即比较的内容要一致)。浮点型实现了PartialOrd
但是没有实现Ord
,因为NaN < 0 == false
和NaN >= 0 == false
都为真。
/* ... */
impl Eq for Person {}
impl Ord for Person {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.age.cmp(&other.age)
}
}
完成 PartialOrd
后会为你的类型提供lt()
、le()
、gt()
、ge()
的比较操作。
完成 Ord
后会为你的类型提供 max()
、min()
和 clamp()
(比较该值是否在某个区间内)。
From & Into
我们都知道 Rust 中的 as
可以用于一些基本类型的简单转换,如果涉及到自定义类型的转换,我们可以为它们实现From
和 Into
trait 以更方便地转换类型。
我们先定义如下两个结构体。
struct Dog {
weight: usize,
height: usize,
}
struct People {
iq: isize,
eq: isize,
}
From
实现了 From<Dog>
trait 后会为你的类型提供 from()
、into()
、try_from()
和 try_into()
方法,使用 try_from()
和try_into()
方法始终是正确的,使用 into()
和 try_into()
方法时要写上类型注解,否则 Rust 不知道你想转换成什么类型。
impl From<Dog> for People {
fn from(d: Dog) -> Self {
People {
iq: d.weight as isize + 10,
eq: -1 * d.height as isize,
}
}
}
fn main() {
let d1 = Dog {weight: 20, height: 20};
let d2 = Dog {weight: 30, height: 30};
let p1 = People::from(d);
let p2: People = d2.into();
}
Into
如果你想将 Dog
转为 People
类型,只实现 From
或者 Into
trait 即可,否则会造成矛盾。
impl Into<People> for Dog {
fn into(self) -> People {
People {
iq: self.weight as isize + 10,
eq: -1 * self.height as isize,
}
}
}
fn main() {
// ... 和from一样的用法
}
TryFrom & TryInto
这一组 trait 作用和上一组相同,区别是这一组通常用于转换容易出错的类型。
TryFrom
实现了 TryFrom
trait 后会自动为你的类型实现 try_from()
、try_into()
。
impl TryFrom<Dog> for People {
type Error = String;
fn try_from(value: Dog) -> Result<Self, Self::Error> {
if value.height < 0 as usize || value.weight < 0 as usize {
Err(String::from("转换出错 => Dog to People"))
} else {
Ok(People {
iq: value.weight as isize,
eq: value.height as isize,
})
}
}
}
fn main() {
let d1 = Dog{weight: 20, height: 20};
let d2 = Dog{weight: 20, height: 20};
let p1 = People::try_from(d).unwrap();
let p2 = d2.try_into().unwrap();
}
TryInto
实现了 TryInto
trait 后会自动为你的类型提供 try_into()
方法。
impl TryInto<People> for Dog {
type Error = ();
fn try_into(self) -> Result<People, Self::Error> {
Err(())
}
}
fn main() {
let d = Dog {
weight: 20,
height: 20,
};
let p: People = d.try_into().unwrap();
}
ToString & FromStr
ToString
要把任何类型转换成 String
,只需要实现那个类型的 ToString
trait。然而不要直接这么做,您应该实现 fmt::Display
trait,它会自动提供 ToString
,并且还可以用来打印类型。
use std::fmt;
struct Circle {
radius: i32
}
impl fmt::Display for Circle {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Circle of radius {}", self.radius)
}
}
// ToString版本实现
impl ToString for Circle {
fn to_string(&self) -> String {
format!("Circle of radius {:?}", self.radius)
}
}
fn main() {
let circle = Circle { radius: 6 };
println!("{}", circle.to_string());
}
FromStr
#[derive(Debug)]
struct People {
name: String,
age: usize,
}
impl std::str::FromStr for People {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
let ss: Vec<&str> = s.split(";").collect();
if ss.len() == 2 {
match ss[1].parse::<usize>() {
| Ok(age) => Ok(People {
name: ss[0].to_string(),
age: age,
}),
| Err(e) => Err(()),
}
} else {
Err(())
}
}
}
fn main() {
let s = "Mr.Li;20";
println!("{:?}", s.parse::<People>()); // Ok(People { name: "Mr.Li", age: 20 })
}