Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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这个结构体实现了Clone trait
  • 主函数中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. 建议实现PartialEqPartialOrdHashEqOrd 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

PartialOrdPartialEqEqOrd trait

PartialOrd:提供 <<=>>= 操作符支持,允许自定义类型进行部分排序比较,可能存在无法比较的情况

Eq:是 PartialEq 的更严格版本,要求相等性满足自反性(a == a 总是 true)。实现Eq必须要先实现PartialEq。

Ord:是 PartialOrd 的更严格版本,要求实现全序关系,使类型支持完整的排序逻辑(如 BTreeMapBTreeSet) 。实现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() 方法,以支持 HashMapHashSet 等哈希集合。Hash trait 本身并不以 Eq 为超 trait,但哈希集合要求 K: Eq + Hash,且相等的值必须产生相同的哈希。所以在实践中,只要类型会用作哈希表/集合的键,就应一并实现 PartialEqEq

看个例子:

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,
    },
}

EqPartialEqOrdPartialOrd

Eq相对于PartialEqOrd相对于PartialOrd都有额外的语义要求。只应在这些语义适用你的类型时才实现它们。

我们看一下Eq相对于PartialEq的额外语义:

  • 自反性(Reflexivity):对于所有 a,必须有 a == a 恒成立

PartialEq 本身已要求对称性传递性Eq 是“相等性也自反”的标记,因此不存在诸如 f32::NAN != f32::NAN 这类“部分相等”。)

我们看一下Ord相对于PartialOrd的额外语义:

  • 全序 / 可比性:对所有 aba < ba == ba > b 恰好成立其一(等价地,partial_cmp 从不返回 None
  • 自反性(Reflexivity):对于所有a,必须有a <= aa >= a恒成立。
  • 反对称性(Antisymmetry):如果a <= bb <= a,则必须保证a == b
  • 传递性(Transitivity):如果 a <= bb <= c,则必须保证 a <= c