16.7Operator overloading with std::ops

Last updated June 13, 2026

Here's a small revelation that captures the whole chapter. When you write 3 + 4, the + is calling a method of a trait named Add. Every operator in Rust, +, -, *, [], unary -, works this way: it's syntax for a trait method. Which means operator overloading, making + work on your own types, isn't a special feature at all. It's just implementing a trait, the same skill you've used for Summary and Display. This lesson is one page; the C++ chapter it replaces is sixteen lessons, and the difference is exactly because Rust didn't make operators special.

+ is the Add trait

The operator traits live in std::ops. Addition is Add, and implementing it for your type makes + work on it:

use std::ops::Add;

#[derive(Debug, Clone, Copy)]
struct Point {
    x: f64,
    y: f64,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    let a = Point { x: 1.0, y: 2.0 };
    let b = Point { x: 3.0, y: 4.0 };
    println!("{:?}", a + b);
}
Point { x: 4.0, y: 6.0 }

a + b calls add(a, b) under the hood, because we implemented Add for Point. The implementation has two parts: type Output = Point declares what type the addition produces (an associated type, the trait saying "implementers specify what + returns"), and fn add defines the operation, here adding the points componentwise. Once this exists, + on two Points is as ordinary as + on two numbers, because for numbers it was always this same trait.

Key insight

Operators in Rust are syntax sugar for trait methods: a + b is Add::add(a, b), a * b is Mul::mul(a, b), a[i] is Index::index(a, i). This is why "operator overloading" needs no dedicated feature, no special rules, no separate chapter. It falls out of traits for free. The same mechanism that let you add a summarize method lets you add a + operator, because to Rust they're the same kind of thing.

The other operator traits

Each operator maps to a trait in std::ops, and implementing it follows the same pattern. A sampling:

Mul is * (with type Output and fn mul), so you could scale a Point by a number. Sub is -, Div is /, Rem is %. Neg is the unary minus, so -point works:

use std::ops::Neg;

impl Neg for Point {
    type Output = Point;
    fn neg(self) -> Point {
        Point { x: -self.x, y: -self.y }
    }
}

Index is the [] indexing operator, letting collection[i] work on your own type (it's how Vec and HashMap support [], lessons 18.2 and 18.6). AddAssign is +=, MulAssign is *=, and so on for the compound-assignment operators (lesson 6.3). The pattern never changes: find the trait for the operator in std::ops, implement its one method, and the operator works on your type.

When to overload, and when not to

A power this clean is easy to misuse. The guiding rule: overload an operator only when its meaning is obvious and conventional for your type. Adding two Points componentwise is obvious; everyone reading a + b for points expects exactly that. Defining + to mean "concatenate these two database records" is not obvious, and a reader will be misled by the familiar symbol. The whole value of an operator is that it carries a shared expectation; overloading it to mean something surprising squanders that.

Best practice

Implement operator traits only when the operator's standard meaning maps cleanly onto your type: arithmetic on math-like types (vectors, points, money, matrices), indexing on collection-like types. If you find yourself writing a comment to explain what + does on your type, it shouldn't be +, make it a named method instead. Clear method names beat clever operators whenever the operator's meaning isn't instantly obvious.

The C++ comparison, stated outright

This lesson is deliberately short, and that brevity is the point. C++ treats operator overloading as a major topic: separate rules for member versus non-member operators, friend functions, return-by-value versus reference, the assignment operators, the subscript operator, conversion operators, a whole chapter's worth. Rust has one uniform answer for all of it: operators are traits, implement the trait. There's nothing to learn here beyond "which trait is which operator," because you already know how to implement a trait. That collapse, sixteen lessons into one, is the clearest single illustration of why a Rust course can be shorter than a C++ one without covering less.

Quiz time

Question #1

When you write a + b, what is actually being called, and what does that imply about operator overloading?

Show solution

a + b calls the add method of the Add trait: Add::add(a, b). So operators are syntax for trait methods, which means "overloading" an operator is just implementing the corresponding trait for your type. There's no special operator-overloading feature; it falls out of traits.

Question #2

What two parts does an impl Add for Point need, and what does each do?

Show solution

type Output = ..., an associated type declaring what type the + produces (e.g. Point), and fn add(self, other: Point) -> Point, the method defining the actual operation. The Output type lets the trait support operators that return a different type than their operands; add does the work.

Question #3

Should you implement Add to concatenate two unrelated records? Why or why not?

Show solution

No. Overload an operator only when its conventional meaning maps obviously onto your type (componentwise addition of points, scaling, indexing collections). + meaning "concatenate records" is surprising, and a reader seeing a + b will expect arithmetic, not that. When the meaning isn't instantly obvious, use a named method instead. The operator's value is its shared expectation.

Operators are conversions of a sort, turning operands into a result. The next lesson covers actual conversions, the From and Into traits, which pay off lesson 4.10's IOU about the "blessed" way to convert between types and reveal how the ? operator quietly converts error types.