🎓 A1Learner
🔍
0 of 149 lessons completed

Rust Operator Overloading

Operator overloading gives a known operator a meaning for a new type. Implementing the Add trait gives a type the + operator.

Give a type the + operator

Use std::ops::Add to give a type the + operator. Add asks for an associated type: type Output is the type that a + b produces:

impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point { x: self.x + other.x, y: self.y + other.y }
    }
}

Now + works on Point

With the impl in place, the + sign runs our add method:

let sum = Point { x: 1, y: 0 } + Point { x: 2, y: 3 };
println!("{:?}", sum);

Add unlike types with Rhs

The other side has a generic type named Rhs, short for right-hand side. It defaults to Self — leave it out and both sides are the same type. Set it yourself to add unlike types:

impl Add<Meters> for Millimeters {
    type Output = Millimeters;
    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + other.0 * 1000)
    }
}
let total = Millimeters(250) + Meters(1);
println!("total = {} mm", total.0);

One trait per operator

The same std::ops family covers Sub, Mul, Div, Rem, Neg, Not and the bit traits. There is one trait per operator. Each is open to your types. Here Neg gives Point a minus sign:

impl Neg for Point {
    type Output = Point;
    fn neg(self) -> Point { Point { x: -self.x, y: -self.y } }
}
println!("{:?}", -Point { x: 1, y: -2 });

An operator is a method call

An operator is shorthand for a method call: a + b runs a.add(b). The two values below are built in exactly the same way:

let p = Point { x: 1, y: 0 } + Point { x: 2, y: 3 };
let q = Point { x: 1, y: 0 }.add(Point { x: 2, y: 3 });
println!("{:?} {:?}", p, q);

Example

use std::ops::Add;
#[derive(Debug, PartialEq)]
struct Point { x: i32, y: i32 }
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point { Point { x: self.x + other.x, y: self.y + other.y } }
}
#[derive(Debug)]
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
    type Output = Millimeters;
    fn add(self, other: Meters) -> Millimeters { Millimeters(self.0 + (other.0 * 1000)) }
}
fn main() {
    println!("{:?}", Point { x: 1, y: 0 } + Point { x: 2, y: 3 });
    let total = Millimeters(1000) + Meters(2);
    println!("total = {} mm", total.0);
}

Key points

  • Use a default type parameter to extend a trait later without breaking code that already uses it
  • Each direction is its own impl.
  • Foo + Bar and Bar + Foo are two separate impls.
  • They may produce different Output types.
  • Addition for your types does not have to commute.
  • Rust fixes the set of operators, and no new symbols can be invented.
  • Implementing an operator trait teaches a known operator to work on a new type.

In this example, Point uses the default Rhs. So Point + Point works. It adds x to x and y to y. The result is Point { x: 3, y: 3 }. Millimeters sets Rhs to Meters instead. Its add converts the metres to millimetres first: 1000 + 2 * 1000 gives total = 3000 mm. One + sign, two different right-hand types, each chosen in the impl line.

Note

Overloading never changes what + means for numbers.

It only adds a meaning for your type, written by you in the add method.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

What does Rhs = Self in the Add trait mean?