【Rust中级教程】2.2. API设计原则之不意外性(unsurprising) Pt.2:实现Clone、Default、PartialEq、PartialOrd、Hash、Eq和Ord

【Rust中级教程】2.2. API设计原则之不意外性(unsurprising) Pt.2:实现Clone、Default、PartialEq、PartialOrd、Hash、Eq和Ord
2.2. API设计原则之不意外性(unsurprising) Pt.2实现Clone、Default、PartialEq、PartialOrd、Hash、Eq和Ord2.2.1. 建议实现Clonetrait和DefaulttraitClonetraitRust的Clonetrait允许实现者通过clone方法显式创建自身的深拷贝以区别于Copytrait提供的按值复制。看一个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 }DefaulttraitRust的Defaulttrait允许类型定义一个默认值通过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和OrdtraitPartialEqtraitPartialEq提供和!操作符支持允许自定义类型进行部分相等性比较看个例子#[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: falsePartialOrd、PartialEq、Eq、OrdtraitPartialOrd提供、、和操作符支持允许自定义类型进行部分排序比较可能存在无法比较的情况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(), Alices Age); ages.insert(person2.clone(), Bobs Age); ages.insert(person3.clone(), Charlies Age); for (person, description) in ages { println!({}: {} - {:?}, person.name, person.age, description); } }BTreeMap实现了有序存储。它需要根据键的值来排序所以它要求键类型必须实现了Ord全局比较用于排序和Eq用于判断键是否相等。实现Ord又需要先实现PartialOrd实现Eq有需要先实现PartialEqHashtraitHash允许类型实现hash()方法以支持HashMap、HashSet等哈希集合。Hashtrait 本身并不以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 hashH: 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与PartialOrdEq相对于PartialEq、Ord相对于PartialOrd都有额外的语义要求。只应在这些语义适用你的类型时才实现它们。我们看一下Eq相对于PartialEq的额外语义-自反性Reflexivity对于所有a必须有a a恒成立PartialEq本身已要求对称性和传递性Eq是“相等性也自反”的标记因此不存在诸如f32::NAN ! f32::NAN这类“部分相等”。我们看一下Ord相对于PartialOrd的额外语义-全序 / 可比性对所有a和ba 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。