【2025 Rust学习 --- 10 运算符重载】

重载操作符

算术运算符与按位运算符

Rust 中,表达式 a + b 实际上是 a.add(b) 的简写形式,也就是对标准库 中 std::ops::Add 特型的 add 方法的调用。Rust 的标准数值类型都实现了 std::ops::Add。

rust 复制代码
trait Add<Rhs = Self> {
 type Output;
 fn add(self, rhs: Rhs) -> Self::Output;
}

复数实现特型

rust 复制代码
use std::ops::Add;
impl Add for Complex<i32> {
 type Output = Complex<i32>;
 fn add(self, rhs: Self) -> Self {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

这只实现了i32的支持,使用泛型支持更多:

rust 复制代码
use std::ops::Add;
impl<T> Add for Complex<T> where T: Add<Output=T>,
{
 type Output = Self;
 fn add(self, rhs: Self) -> Self {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

where T: Add,将 T 限界到能与自身相加并产生 另一个 T 值的类型。

Add 特型不要求 + 的两个操作数具有相同的类型,也不限制结果类型。因 此,一个尽可能泛化的实现可以让左右操作数独立变化,并生成加法所能生成的 任何组件类型的 Complex 值:

rust 复制代码
use std::ops::Add;
impl<L, R> Add<Complex<R>> for Complex<L>
where
 L: Add<R>,
{
 type Output = Complex<L::Output>;
 fn add(self, rhs: Complex<R>) -> Self::Output {
     Complex {
         re: self.re + rhs.re,
         im: self.im + rhs.im,
     }
 }
}

一元运算符

Rust 的所有带符号数值类型都实现了 std::ops::Neg,以支持一元取负运算 符 -;整数类型和 bool 实现了 std::ops::Not,以支持一元取反运算符 !。 还有一些是针对这些类型的引用的实现。

! 运算符会对 bool 值进行取反,而对整数执行按位取反,扮演 着 C 和 C++ 中的 ! 运算符和 ~ 运算符。

二元运算符

Rust 不允许 + 的左操作数是 &str 类型,以防止通过在左侧重复接入小型 片段来构建长字符串

(这种方式性能不佳,其时间复杂度是字符串最终长度的平方,一般来说,write! 宏更适合从小型片段构建出字符串)

复合赋值运算符

在 Rust 中,x += y 是方法调用 x.add_assign(y) 的简写形式,其中 add_assign 是 std::ops::AddAssign 特型的唯一方法:

rust 复制代码
trait AddAssign<Rhs = Self> {
 fn add_assign(&mut self, rhs: Rhs);
}

Rust 的所有数值类型都实现了算术复合赋值运算符。Rust 的整数类型和 bool 类型都实现了按位复合赋值运算符。

rust 复制代码
use std::ops::AddAssign;
impl<T> AddAssign for Complex<T>
where
 T: AddAssign<T>,
{
 fn add_assign(&mut self, rhs: Complex<T>) {
     self.re += rhs.re;
     self.im += rhs.im;
 }
}

实现 std::ops::Add并不会自动实现 std::ops::AddAssign,如果想让 Rust 允许你的类型作为 += 运算符的左操作数,就必须自行实现 AddAssign。

相等性比较

Rust 的相等性运算符 == 和 != 是对调用 std::cmp::PartialEq 特型的 eq 和 ne 这两个方法的简写:

rust 复制代码
assert_eq!(x == y, x.eq(&y));
assert_eq!(x != y, x.ne(&y));

trait PartialEq<Rhs = Self>
where
 Rhs: ?Sized,
{
     fn eq(&self, other: &Rhs) -> bool;
     fn ne(&self, other: &Rhs) -> bool {
     !self.eq(other)
 }
}

impl<T: PartialEq> PartialEq for Complex<T> {
 fn eq(&self, other: &Complex<T>) -> bool {
 self.re == other.re && self.im == other.im
 }
}

PartialEq 的实现就是将左操作数的每个字段与右操 作数的相应字段进行比较。

只需把 PartialEq 添加到类型定义的 derive 属性中即可避免书写:

rust 复制代码
#[derive(Clone, Copy, Debug, PartialEq)]
struct Complex<T> {
 ...
}

Rust 自动生成的实现与手写的代码本质上是一样的,都会依次比较每个字段或 类型的元素。

与按值获取操作数的算术特型和按位运算特型不同,PartialEq 会通过引用获 取其操作数。这意味着在比较诸如 String、Vec 或 HashMap 之类的非 Copy 值时并不会导致它们被移动

rust 复制代码
where
 Rhs: ?Sized,

上面这种书写放宽了 Rust 对类型参数必须有固定大小的常规要求,能让我们写出像 PartialEq 或 PartialEq<[T]> 这样的特型。

str 实现了 PartialEq,下面两种等效:

RUST 复制代码
assert!("ungula" != "ungulate");
assert!("ungula".ne("ungulate"));

像 0.0/0.0 和其他没 有适当值的表达式必须生成特殊的非数值,通常叫作 NaN 值。该标准进一步要 求将 NaN 值视为与包括其自身在内的所有其他值都不相等

如果你的泛型代码想要 "完全相等"关系,那么可以改用 std::cmp::Eq 特型作为限界,它表示完全相 等关系

如果类型实现了 Eq,则对于该类型的每个值 x,x == x 都必须为 true。

几乎所有实现了 PartialEq 的类型都实现了 Eq,而 f32 和 f64 是标准库中仅有的两个属于 PartialEq 却不属于 Eq 的类型

Eq 定义为 PartialEq 的扩展

有序比较

Rust 会根据单个特型 std::cmp::PartialOrd 来定义全部的有序比较运算符 <、>、<= 和 >= 的行为:

rust 复制代码
trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
where
 Rhs: ?Sized,
{
 fn partial_cmp(&self, other: &Rhs) -> Option<Ordering>;
 fn lt(&self, other: &Rhs) -> bool { ... }
 fn le(&self, other: &Rhs) -> bool { ... }
 fn gt(&self, other: &Rhs) -> bool { ... }
 fn ge(&self, other: &Rhs) -> bool { ... }
}

PartialOrd 扩展了 PartialEq:只有可以比较相等性 的类型才能比较顺序性。

rust 复制代码
enum Ordering {
 Less, // self < other
 Equal, // self == other
 Greater, // self > other
}

如果 partial_cmp 返回 None,那么就意味着 self 和 other 相对于彼 此是无序的,即两者都不大于对方,但也不相等。在 Rust 的所有原始类型中, 只有浮点值之间的比较会返回 None:具体来说,将 NaN 值与任何其他值进行 比较都会返回 None。

要比较 Left 和 Right 这两种类型的值,那么 Left 就必须实现 PartialOrd。像 x < y 或 x >= y 这样的表达式 都是调用 PartialOrd 方法的简写形式

rust 复制代码
trait Ord: Eq + PartialOrd<Self> {
 fn cmp(&self, other: &Self) -> Ordering;
}

cmp 方法只会返回 Ordering,而不会像 partial_cmp 那样返回 Option:cmp 总会声明它的两个参数相等或指出它们的相对顺 序。几乎所有实现了 PartialOrd 的类型都应该实现 Ord。在标准库中,f32 和 f64 是该规则的例外情况

希望按上限排序一些区间,那么很容易用 sort_by_key 来实现:

intervals.sort_by_key(|i| i.upper);

Index 与 IndexMut

通过实现 std::ops::Index 特型和 std::ops::IndexMut 特型,你可以规 定像 a[i] 这样的索引表达式该如何作用于你的类型。数组可以直接支持 [] 运 算符

a[i] 通常是 *a.index(i) 的简写形式, 其中 index 是 std::ops::Index 特型的方法。但是,如果表达式被赋值或 借用成了可变形式,那么 a[i] 就是对调用 std::ops::IndexMut 特型方法 的 *a.index_mut(i) 的简写。

rust 复制代码
trait Index<Idx> {
 type Output: ?Sized;
 fn index(&self, index: Idx) -> &Self::Output;
}
trait IndexMut<Idx>: Index<Idx> {
 fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}

使用单个 usize 对 切片进行索引,以引用单个元素,因为切片实现了 Index。还可以使 用像 a[i...j] 这样的表达式来引用子切片,因为切片也实现了 Index<Range<usize>>

原型:*a.index(std::ops::Range { start: i, end: j })

Rust 的 HashMap 集合和 BTreeMap 集合允许使用任何可哈希类型或有序类型 作为索引。以下代码之所以能运行,是因为 HashMap<&str, i32> 实现了 Index<&str>:

rust 复制代码
use std::collections::HashMap;
let mut m = HashMap::new();
m.insert("十", 10);
m.insert("百", 100);
m.insert("千", 1000);
assert_eq!(m["十"], 10);
assert_eq!(m["千"], 1000);

当索引表达式出现在需要可变 引用的上下文中时,Rust 会自动选择 index_mut

一下两种相等:

rust 复制代码
let mut desserts =
 vec!["Howalon".to_string(), "Soan papdi".to_string()];
desserts[0].push_str(" (fictional)");
desserts[1].push_str(" (real)");

use std::ops::IndexMut;
(*desserts.index_mut(0)).push_str(" (fictional)");
(*desserts.index_mut(1)).push_str(" (real)");

IndexMut 有一个限制,即根据设计,它必须返回对某个值的可变引用。这就是 不能使用像 m[" 十 "] = 10;

的赋值语句。因为创建这么一个马上就会因赋值而被丢弃的可变引用是一种浪费

编写 image[row][column] 时,如果 row 超出范围,那么 .index() 方法在试图索引 self.pixels 时也会超出范围,从而引发 panic。 这就是 Index 实现和 IndexMut 实现的行为方式:检测到越界访问并导致 panic,就像索引数组、切片或向量时越界一样。

其他运算符

并非所有运算符都可以在 Rust 中重载。

  • 从 Rust 1.50 开始,错误检查运算符 ? 仅适用于 Result 值和 Option 值

  • 逻辑运算符&& 和 ||仅限于 bool 值。

  • .. 运算符和 ..= 运算符总 会创建一个表示范围边界的结构体

  • & 运算符总是会借用引用

  • = 运算符总是会 移动值或复制值。它们都不能重载。

  • 解引用运算符 *val 和用于访问字段和调用方法的点运算符(如 val.field 和 val.method())可以用 Deref 特型和 DerefMut 特型进行重载,后序介绍。

  • Rust 不支持重载函数调用运算符 f(x)。当你需要一个可调用的值时,通常只需 编写一个闭包即可。后面介绍 Fn、FnMut 和 FnOnce 这几个特殊特型。

相关推荐
星小辰一1 分钟前
【形式篇】年终总结怎么写:PPT如何将内容更好地表现出来
经验分享·学习·powerpoint
LuckyLay4 分钟前
Golang学习笔记_20——error
笔记·学习·golang·error
一棵开花的树,枝芽无限靠近你4 分钟前
【PPTist】批注、选择窗格
前端·笔记·学习·编辑器·pptist
一棵开花的树,枝芽无限靠近你5 分钟前
【PPTist】查找替换、绘制文本框
javascript·笔记·学习·编辑器·pptist
虾球xz25 分钟前
游戏引擎学习第77天
学习·游戏引擎
前端小咸鱼一条27 分钟前
axios的学习笔记
笔记·学习
东京老树根33 分钟前
Excel 技巧03 - 如何对齐小数位数? (★)如何去掉小数点?如何不四舍五入去掉小数点?
笔记·学习·excel
rongjv1 小时前
[rustGUI][iced]基于rust的GUI库iced(0.13)的部件学习(01):为窗口设置布局(column、row)
rust·gui·iced
阿阳微客1 小时前
CS·GO搬砖流程详细版
笔记·学习
无心所欲为风2 小时前
pcie学习记录(1):基于xdma的工程搭建
学习