Operators

Operator overloading is implemented via traits in std::ops:

1. ```
   #[derive(Debug, Copy, Clone)]
   ```
2. ```
   struct Point {
   ```
3. ```
       x: i32,
   ```
4. ```
       y: i32,
   ```
5. ```
   }
   ```

7. ```
   impl std::ops::Add for Point {
   ```
8. ```
       type Output = Self;
   ```

10. ```
        fn add(self, other: Self) -> Self {
    ```
11. ```
            Self { x: self.x + other.x, y: self.y + other.y }
    ```
12. ```
        }
    ```
13. ```
    }
    ```

15. ```
    fn main() {
    ```
16. ```
        let p1 = Point { x: 10, y: 20 };
    ```
17. ```
        let p2 = Point { x: 100, y: 200 };
    ```
18. ```
        println!("{p1:?} + {p2:?} = {:?}", p1 + p2);
    ```
19. ```
    }
    ```

This slide should take about 5 minutes.

Discussion points:

  • You could implement Add for &Point. In which situations is that useful?
  • Answer: Add:add consumes self. If type T for which you are overloading the operator is not Copy, you should consider overloading the operator for &T as well. This avoids unnecessary cloning on the call site.
  • Why is Output an associated type? Could it be made a type parameter of the method?
  • Short answer: Function type parameters are controlled by the caller, but associated types (like Output) are controlled by the implementer of a trait.
  • You could implement Add for two different types, e.g. impl Add<(i32, i32)> for Point would add a tuple to a Point.

The Not trait (! operator) is notable because it does not “boolify” like the same operator in C-family languages; instead, for integer types it negates each bit of the number, which arithmetically is equivalent to subtracting it from -1: !5 == -6.