运算符与重载

一元操作符

顾名思义,一元操作符是专门对一个Rust元素进行操纵的操作符,主要包括以下几个:

  • -: 取负,专门用于数值类型。
  • *: 解引用。这是一个很有用的符号,和DerefDerefMut)这个trait关联密切。
  • !: 取反。取反操作相信大家都比较熟悉了,不多说了。有意思的是,当这个操作符对数字类型使用的时候,会将其每一位都置反!也就是说,你对一个1u8进行!的话你将会得到一个254u8
  • &&mut: 租借,borrow。向一个owner租借其使用权,分别是租借一个只读使用权和读写使用权。

二元操作符

算数操作符

算数运算符都有对应的trait的,他们都在std::ops下:

  • +: 加法。实现了std::ops::Add
  • -: 减法。实现了std::ops::Sub
  • *: 乘法。实现了std::ops::Mul
  • /: 除法。实现了std::ops::Div
  • %: 取余。实现了std::ops::Rem

位运算符

和算数运算符差不多的是,位运算也有对应的trait。

  • &: 与操作。实现了std::ops::BitAnd
  • |: 或操作。实现了std::ops::BitOr
  • ^: 异或。实现了std::ops::BitXor
  • <<: 左移运算符。实现了std::ops::Shl
  • >>: 右移运算符。实现了std::ops::Shr

惰性boolean运算符

逻辑运算符有三个,分别是&&||!。其中前两个叫做惰性boolean运算符,之所以叫这个名字。是因为在Rust里也会出现其他类C语言的逻辑短路问题。有点不同的是Rust里这个运算符只能用在bool类型变量上。如 1 && 1 之类的表达式无效。

比较运算符

比较运算符其实也是某些trait的语法糖啦,不同的是比较运算符所实现的trait只有两个std::cmp::PartialEqstd::cmp::PartialOrd

其中, ==!=实现的是PartialEq。 而,<>>=<=实现的是PartialOrd

std::cmp有四个trait,Rust对于这四个trait的处理是明确的。IEEE的浮点数有一个特殊的值叫NaN,这个值表示未定义的一个浮点数。在Rust中可以用0.0f32 / 0.0f32来求得其值。这个数他是一个确定的值,但它表示的是一个不确定的数! NaN != NaN 的结果是 true 。但这么写又不符合Eq的定义里total equal(每一位一样两个数就一样)的定义。因此有了PartialEq这么一个定义,我们只支持部分相等好吧,NaN这个情况我就特指。

Rust编译器选择了PartialOrdPartialEq来作为其默认的比较符号的trait。

类型转换运算符

运算符as

  1. fn avg(vals: &[f64]) -> f64 {
  2. let sum: f64 = sum(vals);
  3. let num: f64 = len(vals) as f64;
  4. sum / num
  5. }

Rust 允许运算符重载。特定的运算符可以被重载。要支持一个类型间特定的运算符,你可以实现一个的特定的重载运算符的trait。

例如,+运算符可以通过Add特性重载:

  1. use std::ops::Add;
  2. #[derive(Debug)]
  3. struct Point {
  4. x: i32,
  5. y: i32,
  6. }
  7. impl Add for Point {
  8. type Output = Point;
  9. fn add(self, other: Point) -> Point {
  10. Point { x: self.x + other.x, y: self.y + other.y }
  11. }
  12. }
  13. fn main() {
  14. let p1 = Point { x: 1, y: 0 };
  15. let p2 = Point { x: 2, y: 3 };
  16. let p3 = p1 + p2;
  17. println!("{:?}", p3);
  18. }

main中,我们可以对我们的两个Point+号,因为我们已经为Point实现了Add<Output=Point>

有一系列可以这样被重载的运算符,并且所有与之相关的trait都在std::ops模块中。查看它的文档来获取完整的列表。

实现这些特性要遵循一个模式。让我们仔细看看Add

  1. # mod foo {
  2. pub trait Add<RHS = Self> {
  3. type Output;
  4. fn add(self, rhs: RHS) -> Self::Output;
  5. }
  6. # }

这里总共涉及到3个类型:你impl Add的类型,RHS,它默认是Self,和Output。对于一个表达式let z = x + yxSelf类型的,yRHS,而zSelf::Output类型。

  1. # struct Point;
  2. # use std::ops::Add;
  3. impl Add<i32> for Point {
  4. type Output = f64;
  5. fn add(self, rhs: i32) -> f64 {
  6. // add an i32 to a Point and get an f64
  7. # 1.0
  8. }
  9. }

这个Output是肿么回事:类型转换!我们在现实中会出现0.5+0.5=1这样的算式,用Rust的语言来描述就是: 两个f32相加得到了一个i8。显而易见,Output就是为这种情况设计的。

  1. use std::ops::Add;
  2. #[derive(Debug)]
  3. struct Complex {
  4. a: f64,
  5. b: f64,
  6. }
  7. impl Add for Complex {
  8. type Output = Complex;
  9. fn add(self, other: Complex) -> Complex {
  10. Complex {a: self.a+other.a, b: self.b+other.b}
  11. }
  12. }
  13. impl Add<i32> for Complex {
  14. type Output = f64;
  15. fn add(self, other: i32) -> f64 {
  16. self.a + self.b + (other as f64)
  17. }
  18. }
  19. fn main() {
  20. let cp1 = Complex{a: 1f64, b: 2.0};
  21. let cp2 = Complex{a: 5.0, b:8.1};
  22. let cp3 = Complex{a: 9.0, b:20.0};
  23. let complex_add_result = cp1 + cp2;
  24. print!("{:?}\n", complex_add_result);
  25. print!("{:?}", cp3 + 10i32);
  26. }

输出结果:

  1. Complex { a: 6, b: 10.1 }
  2. 39

对范型的限制

Rust的运算符是基于trait系统的,同样的,运算符可以被当成一种对范型的限制,我们可以这么要求范型T必须实现了trait Mul<Output=T>

现在我们知道了运算符 trait 是如何定义的了,我们可以更通用的定义HasArea trait 和Square结构体:

  1. use std::ops::Mul;
  2. trait HasArea<T> {
  3. fn area(&self) -> T;
  4. }
  5. struct Square<T> {
  6. x: T,
  7. y: T,
  8. side: T,
  9. }
  10. impl<T> HasArea<T> for Square<T>
  11. where T: Mul<Output=T> + Copy {
  12. fn area(&self) -> T {
  13. self.side * self.side
  14. }
  15. }
  16. fn main() {
  17. let s = Square {
  18. x: 0.0f64,
  19. y: 0.0f64,
  20. side: 12.0f64,
  21. };
  22. println!("Area of s: {}", s.area());
  23. }

对于trait HasArea<T>和 struct Square<T>,我们通过where T: Mul<Output=T> + Compy 限制了T必须实现乘法。同时Copy则限制了Rust不再将self.side给move到返回值里去。