2.2. API设计原则之不意外性(unsurprising) Pt.2:实现Clone、Default、PartialEq、PartialOrd、Hash、Eq和Ord
2.2.1. 建议实现Clone trait和Default trait
Clone trait
Rust的Clone trait允许实现者通过clone方法显式创建自身的深拷贝,以区别于Copy trait提供的按值复制。
看一个Clone例子:
#[derive(Debug, Clone)]
struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: String, age: u32) -> Self {
Self { name, age }
}
}
fn main() {
let person1 = Person::new("John".to_owned(), 25);
let person2 = person1.clone();
println!("{:?}", person1);
println!("{:?}", person2);
}
Person这个结构体实现了Clonetrait- 主函数中
person2克隆了person1的数据,因为它实现了Clone
输出:
Person { name: "John", age: 25 }
Person { name: "John", age: 25 }
Default trait
Rust的Default trait允许类型定义一个默认值,通过default()方法返回该类型的默认实例。
看一个Default的例子:
#[derive(Default)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point::default();
println!("Point is at ({}, {})", p.x, p.y);
}
输出:
Point is at (0, 0)
2.2.2. 建议实现PartialEq、PartialOrd、Hash、Eq和Ord trait
PartialEq trait
PartialEq提供 == 和 != 操作符支持,允许自定义类型进行部分相等性比较
看个例子:
#[derive(Debug, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point1: Point = Point { x: 1, y: 2 };
let point2: Point = Point { x: 1, y: 2 };
let point3: Point = Point { x: 3, y: 4 };
println!("point1 == point2: {}", point1 == point2);
println!("point1 == point3: {}", point1 == point3);
}
- 通过实现
PartialEq,我们就可以实现比较结构体是否相等的操作
输出:
point1 == point2: true
point1 == point3: false
PartialOrd、PartialEq、 Eq、Ord trait
PartialOrd:提供 <、<=、> 和 >= 操作符支持,允许自定义类型进行部分排序比较,可能存在无法比较的情况
Eq:是 PartialEq 的更严格版本,要求相等性满足自反性(a == a 总是 true)。实现Eq必须要先实现PartialEq。
Ord:是 PartialOrd 的更严格版本,要求实现全序关系,使类型支持完整的排序逻辑(如 BTreeMap、BTreeSet) 。实现Ord必须要先实现PartialOrd。
看个例子:
use std::collections::BTreeMap;
#[derive(Debug, PartialEq, PartialOrd, Eq, Ord, Clone)]
struct Person {
name: String,
age: u32,
}
fn main() {
let mut ages = BTreeMap::new();
let person1 = Person {
name: String::from("Alice"),
age: 25,
};
let person2 = Person {
name: String::from("Bob"),
age: 30,
};
let person3 = Person {
name: String::from("Charlie"),
age: 20,
};
ages.insert(person1.clone(), "Alice's Age");
ages.insert(person2.clone(), "Bob's Age");
ages.insert(person3.clone(), "Charlie's Age");
for (person, description) in &ages {
println!("{}: {} - {:?}", person.name, person.age, description);
}
}
BTreeMap实现了有序存储。它需要根据键的值来排序,所以它要求键类型必须实现了Ord(全局比较,用于排序)和Eq(用于判断键是否相等)。- 实现
Ord又需要先实现PartialOrd - 实现
Eq有需要先实现PartialEq
- 实现
Hash trait
Hash:允许类型实现 hash() 方法,以支持 HashMap、HashSet 等哈希集合。Hash trait 本身并不以 Eq 为超 trait,但哈希集合要求 K: Eq + Hash,且相等的值必须产生相同的哈希。所以在实践中,只要类型会用作哈希表/集合的键,就应一并实现 PartialEq 和 Eq。
看个例子:
use std::collections::HashSet;
use std::hash::{Hash, Hasher};
#[derive(Debug, PartialEq, Eq, Clone)]
struct Person {
name: String,
age: u32,
}
impl Hash for Person {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
self.age.hash(state);
}
}
fn main() {
let mut persons = HashSet::new();
let person1 = Person {
name: "Alice".to_string(),
age: 25,
};
let person2 = Person {
name: "Bob".to_string(),
age: 30,
};
let person3 = Person {
name: "Charlie".to_string(),
age: 20,
};
persons.insert(person1.clone());
persons.insert(person2.clone());
persons.insert(person3.clone());
println!("Persons: {:#?}", persons);
}
HashSet用于存储唯一的元素集合,而HashMap用于存储键值对。
输出:
Persons: {
Person {
name: "Bob",
age: 30,
},
Person {
name: "Charlie",
age: 20,
},
Person {
name: "Alice",
age: 25,
},
}
Eq与PartialEq、Ord与PartialOrd
Eq相对于PartialEq、Ord相对于PartialOrd都有额外的语义要求。只应在这些语义适用你的类型时才实现它们。
我们看一下Eq相对于PartialEq的额外语义:
- 自反性(Reflexivity):对于所有
a,必须有a == a恒成立
(PartialEq 本身已要求对称性和传递性;Eq 是“相等性也自反”的标记,因此不存在诸如 f32::NAN != f32::NAN 这类“部分相等”。)
我们看一下Ord相对于PartialOrd的额外语义:
- 全序 / 可比性:对所有
a和b,a < b、a == b、a > b恰好成立其一(等价地,partial_cmp从不返回None) - 自反性(Reflexivity):对于所有
a,必须有a <= a和a >= a恒成立。 - 反对称性(Antisymmetry):如果
a <= b且b <= a,则必须保证a == b。 - 传递性(Transitivity):如果
a <= b且b <= c,则必须保证a <= c。