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?