Associated Types and Advanced Traits

Implementing Add

The std::ops::Add trait overloads the binary addition operator (+), allowing custom types to support homogeneous addition (T + T), heterogeneous addition (T + U), and reference-based addition (&T + &T).

Trait Definition

pub trait Add<Rhs = Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}
  • Rhs = Self is a default generic type parameter specifying the right-hand operand's type.
  • type Output is an associated type specifying the result type of self + rhs.
  • add(self, rhs: Rhs) takes both self and rhs by value.

Homogeneous Addition (Rhs = Self)

When adding two values of the same type, omit the <Rhs> argument on Add:

use std::ops::Add;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Bytes(u64);

impl Add for Bytes {
    type Output = Bytes;

    fn add(self, rhs: Bytes) -> Self::Output {
        Bytes(self.0 + rhs.0)
    }
}

fn main() {
    let a = Bytes(1024);
    let b = Bytes(2048);
    println!("Total: {:?}", a + b);
}
Total: Bytes(3072)

Heterogeneous Addition (Rhs != Self)

Specify a concrete Rhs type on Add<Rhs> to support adding two distinct types (such as adding a Duration offset to a Timestamp):

use std::ops::Add;

#[derive(Debug, Clone, Copy)]
struct Meters(f64);

#[derive(Debug, Clone, Copy)]
struct Centimeters(f64);

impl Add<Centimeters> for Meters {
    type Output = Meters;

    fn add(self, rhs: Centimeters) -> Meters {
        Meters(self.0 + rhs.0 / 100.0)
    }
}

fn main() {
    let base = Meters(2.5);
    let delta = Centimeters(75.0);

    let total: Meters = base + delta;
    println!("Total distance: {total:?}");
}
Total distance: Meters(3.25)

Note that impl Add<Centimeters> for Meters enables Meters + Centimeters, not Centimeters + Meters. If addition should be commutative, also implement Add<Meters> for Centimeters.

Implementing Add for References (&T + &T)

For non-Copy types that own heap data (such as big integers, matrices, or polynomials), implementing Add on &T allows callers to write &a + &b without moving and consuming a and b:

use std::ops::Add;

#[derive(Debug, PartialEq)]
struct Polynomial {
    coeffs: Vec<i64>,
}

impl Add for &Polynomial {
    type Output = Polynomial;

    fn add(self, rhs: &Polynomial) -> Polynomial {
        let len = self.coeffs.len().max(rhs.coeffs.len());
        let mut coeffs = Vec::with_capacity(len);
        for i in 0..len {
            let a = self.coeffs.get(i).copied().unwrap_or(0);
            let b = rhs.coeffs.get(i).copied().unwrap_or(0);
            coeffs.push(a + b);
        }
        Polynomial { coeffs }
    }
}

fn main() {
    let p1 = Polynomial { coeffs: vec![1, 2, 3] };
    let p2 = Polynomial { coeffs: vec![4, 5] };

    // Borrowed addition preserves p1 and p2:
    let sum = &p1 + &p2;
    println!("p1:  {:?}", p1.coeffs);
    println!("p2:  {:?}", p2.coeffs);
    println!("sum: {:?}", sum.coeffs);
}
p1:  [1, 2, 3]
p2:  [4, 5]
sum: [5, 7, 3]