部分可派生 Trait

等值比较 PartialEq 与 Eq

数学性质的区别

1. PartialEq (部分相等)

表示类型支持部分等价关系,满足以下性质:

  • 对称性:若 a == b ,则 b == a
  • 传递性​​:若 a == b 且 b == c,则 a == c
  • ​不要求自反性​​(即 a == a 不一定成立)。 例如,浮点数中的 NaN 与自身比较返回 false,因此 f32/f64 仅实现 PartialEq

2. Eq(完全相等)​

PartialEq 的扩展,要求满足​​全等价关系,即额外满足以下性质:

  • ​自反性​​:a == a 必须为 true

只有所有值都能确定相等性的类型(如整数、字符串)才适合实现 Eq

应用场景的差异​​

1. PartialEq 的典型用例​​

  • 浮点数(f32/f64):因 NaN 的存在无法满足自反性;
  • 自定义逻辑中部分字段参与比较(如仅比较 Book 结构体的 isbn 字段);
  • 需要允许某些值无法比较的场景(如学生 id 为 0 时禁止比较)。

2. Eq 的强制要求​​

  • 当类型需要作为哈希容器(如 HashMap)的键时,必须实现 Eq,因为哈希要求相等的键必须有相同的哈希值;
  • 需要严格全序比较的类型(如整数、字符串)。

实现方式与编译器支持​​

1. PartialEq 的实现​​

需手动定义 eq 方法(ne 有默认实现)。

示例:

0
1
2
3
4
impl PartialEq for Book {
    fn eq(&self, other: &Self) -> bool {
        self.isbn == other.isbn
    }
}

若所有字段都参与比较,可通过 #[derive(PartialEq)] 自动派生。

2. Eq 的实现​​

仅需在 PartialEq 基础上添加空实现的标记 trait(impl Eq for Book {}),无额外逻辑。编译器通过标记确保类型满足自反性。

示例对比​​​

1. 浮点数的实现​​

0
1
let nan = f32::NAN;
assert!(nan != nan);  // 合法,因 f32 仅实现 PartialEq

2. 自定义类型的选择​​

若某个结构体的 id 字段可能为 0(表示无效值),则仅实现 PartialEq,避免无效值的错误相等性判断。


总结

特性​ ​数学要求​ ​适用场景​ ​编译器支持​
PartialEq 对称性、传递性 允许部分值不可比较(如 NaN 可派生或手动实现
Eq 全等价关系(含自反性) 哈希键、严格全序比较的类型 需手动标记,依赖 PartialEq 实现

次序比较 PartialOrd 与 Ord

数学性质的区别​​

1. PartialOrd(部分顺序)​​

表示类型支持​​部分顺序关系​​,满足以下性质:

  • 自反性​​:a <= a 必须成立;
  • ​反对称性​​:若 a <= b 且 b <= a,则 a == b
  • ​传递性​​:若 a <= b 且 b <= c,则 a <= c
  • ​不要求所有值都可比较​​。例如,浮点数中的 NaN 无法与任何值(包括自身)比较,因此 f32/f64 仅实现 PartialOrd

2. Ord(完全顺序)​​

PartialOrd 的扩展,要求满足​​全序关系​,即额外满足以下性质:

  • 所有值必须可比较​​,即任意两个值 a 和 b 必须满足 a <= b 或 b <= a。例如,整数类型(i32u64 等)必须实现 Ord。​

应用场景的差异​​

1. PartialOrd 的典型用例​​

  • 浮点数​​:NaN 的存在导致无法全序比较;
  • ​自定义类型存在不可比字段​​:如学生成绩可能因缺考而无法排序;
  • ​部分逻辑排序​​:如仅根据优先级字段排序,其他字段不参与。

2. Ord 的强制要求​​

  • 类型需要作为排序容器(如 BTreeMap)的键时;
  • 需要稳定排序或哈希一致性(如 HashMap 的键需实现 Ord);
  • 需要严格全序的场景(如整数、字符串的字典序)。

实现方式与依赖关系​​

1. PartialOrd 的实现​​

  • 依赖​​:必须已实现 PartialEq
  • ​​方法​​:需定义 partial_cmp,返回 Option<Ordering>(可能为 None);

​示例​​:

0
1
2
3
4
impl PartialOrd for Point {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.x.cmp(&other.x).then(self.y.cmp(&other.y)))
    }
}

若所有字段参与比较,可通过 #[derive(PartialOrd)] 自动派生。

2. Ord 的实现​​

  • ​依赖​​:必须已实现 Eq 和 PartialOrd<Self>
  • ​方法​​:需定义 cmp,返回确定的 Ordering

示例:

0
1
2
3
4
impl Ord for Point {
    fn cmp(&self, other: &Self) -> Ordering {
        self.x.cmp(&other.x).then(self.y.cmp(&other.y))
    }
}

手动实现需确保逻辑与 PartialOrd 一致。

示例对比

1. 浮点数的实现​​

0
1
2
let a = 1.0;
let b = f64::NAN;
assert!(a.partial_cmp(&b).is_none());  // 合法,因 PartialOrd 允许不可比

2. 自定义类型的排序​​

0
1
2
3
4
5
6
7
#[derive(PartialEq, Eq)]
struct Student { id: u32, score: Option<u8> }

impl PartialOrd for Student {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.score.partial_cmp(&other.score) // 仅比较分数,允许无成绩的情况
    }
}

总结

​特性​ ​数学要求​ ​适用场景​ ​实现依赖​
PartialOrd 部分顺序(允许不可比) 浮点数、部分字段排序、不可比类型 需实现 PartialEq
Ord 全序(所有值可比) 哈希键、稳定排序、严格全序类型 需实现 Eq + PartialOrd

复制值 Clone 与 Copy

语义与行为差异

​特性​ ​Copy​ ​Clone​
​复制方式​ 隐式按位复制(bitwise copy),浅拷贝 显式调用 clone(),可能是深拷贝
​所有权影响​ 保留原变量所有权(无移动) 保留原变量所有权,但需显式复制避免移动
​数学性质​ 仅适用于简单值类型,满足按位复制的安全性 适用于任意类型,允许自定义复制逻辑

示例对比​​

0
1
2
3
4
5
6
7
8
// Copy 类型(隐式复制)
let x = 5;
let y = x;  // x 仍有效
println!("x={}, y={}", x, y);  // 允许访问 x

// Clone 类型(显式复制)
let s1 = String::from("hello");
let s2 = s1.clone();  // 必须显式调用
println!("s1={}, s2={}", s1, s2);  // s1 仍有效

实现约束与依赖

1. ​​Copy 的实现条件​​

  • 所有字段必须实现 Copy
  • 类型不能包含析构函数(如 Drop trait);
  • 必须同时实现 Clone(自动派生时编译器处理)。
0
1
#[derive(Copy, Clone)]  // 合法:所有字段为 i32(Copy 类型)
struct Point { x: i32, y: i32 }

2. Clone 的灵活性​​

  • 无字段类型限制,可包含堆数据(如 StringVec);
  • 允许自定义深拷贝逻辑(如克隆时重置引用计数)。
0
1
#[derive(Clone)]
struct Buffer { data: Vec<u8> }  // 合法:Vec 实现 Clone

性能与应用场景

​场景​ ​Copy​ ​Clone​
​适用类型​ 基本类型(i32f64)、简单结构体 堆分配类型(StringVec)、复杂结构体
​性能特征​ 零成本复制(栈内存操作) 可能高开销(深拷贝堆数据)
​典型用途​ 高频复制的轻量级数据(如坐标、配置项) 需要保留原对象的深拷贝场景

性能优化建议​

  • 小尺寸类型(如 <= 16 字节)优先考虑 Copy
  • 避免为大型结构体实现 Copy(可能引发意外内存复制)​。

总结

  • Copy​:当数据简单且复制无副作用时(如基本类型)。
  • Clone​:当数据复杂或需显式控制复制行为时(如包含堆内存的类型)。

相关文章