重载操作符
算术运算符与按位运算符
Rust 中,表达式 a + b 实际上是 a.add(b) 的简写形式,也就是对标准库 中 std::ops::Add 特型的 add 方法的调用。Rust 的标准数值类型都实现了 std::ops::Add。
trait Add<Rhs = Self> {type Output;fn add(self, rhs: Rhs) -> Self::Output;
}
复数实现特型
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的支持,使用泛型支持更多:
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 值:
use std::ops::Add;
impl<L, R> Add<Complex<R>> for Complex<L>
whereL: 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 特型的唯一方法:
trait AddAssign<Rhs = Self> {fn add_assign(&mut self, rhs: Rhs);
}
Rust 的所有数值类型都实现了算术复合赋值运算符。Rust 的整数类型和 bool 类型都实现了按位复合赋值运算符。
use std::ops::AddAssign;
impl<T> AddAssign for Complex<T>
whereT: 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 这两个方法的简写:
assert_eq!(x == y, x.eq(&y));
assert_eq!(x != y, x.ne(&y));trait PartialEq<Rhs = Self>
whereRhs: ?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 属性中即可避免书写:
#[derive(Clone, Copy, Debug, PartialEq)]
struct Complex<T> {...
}
Rust 自动生成的实现与手写的代码本质上是一样的,都会依次比较每个字段或 类型的元素。
与按值获取操作数的算术特型和按位运算特型不同,PartialEq 会通过引用获 取其操作数。这意味着在比较诸如 String、Vec 或 HashMap 之类的非 Copy 值时并不会导致它们被移动
whereRhs: ?Sized,
上面这种书写放宽了 Rust 对类型参数必须有固定大小的常规要求,能让我们写出像 PartialEq 或 PartialEq<[T]> 这样的特型。
str 实现了 PartialEq,下面两种等效:
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 来定义全部的有序比较运算符 <、>、<= 和 >= 的行为:
trait PartialOrd<Rhs = Self>: PartialEq<Rhs>
whereRhs: ?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:只有可以比较相等性 的类型才能比较顺序性。
enum Ordering {Less, // self < otherEqual, // self == otherGreater, // self > other
}
如果 partial_cmp 返回 None,那么就意味着 self 和 other 相对于彼 此是无序的,即两者都不大于对方,但也不相等。在 Rust 的所有原始类型中, 只有浮点值之间的比较会返回 None:具体来说,将 NaN 值与任何其他值进行 比较都会返回 None。
要比较 Left 和 Right 这两种类型的值,那么 Left 就必须实现 PartialOrd
。像 x < y 或 x >= y 这样的表达式 都是调用 PartialOrd 方法的简写形式
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) 的简写。
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>:
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
一下两种相等:
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 这几个特殊特型。