RustArena

Step 2 of 7

Operator traits and default type parameters

In the std-traits module you overloaded + with Add. Here's how std::ops::Add is really declared:

trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}

It uses both tools from the last step. Rhs (the right-hand side) is a generic parameter, because one type can sensibly be added to several others. Output is an associated type, because once you know both sides there's one answer. And Rhs = Self is a default type parameter: if you write impl Add for Money without saying what's on the right, Rust fills in Rhs = Money.

use std::ops::{Add, Mul};

#[derive(Debug, Clone, Copy, PartialEq)]
struct Money {
    cents: u64,
}

// Rhs defaults to Self: Money + Money
impl Add for Money {
    type Output = Money;
    fn add(self, rhs: Money) -> Money {
        Money { cents: self.cents + rhs.cents }
    }
}

// Rhs is u32: Money * u32 (a price times a quantity)
impl Mul<u32> for Money {
    type Output = Money;
    fn mul(self, qty: u32) -> Money {
        Money { cents: self.cents * qty as u64 }
    }
}

fn main() {
    let coffee = Money { cents: 350 };
    let bagel = Money { cents: 225 };
    let total = coffee * 2 + bagel;
    println!("{:?}", total);
    println!("${}.{:02}", total.cents / 100, total.cents % 100);
}
Money { cents: 925 }
$9.25

coffee * 2 calls Mul<u32>, then + bagel calls Add<Money>. Multiplying money by money makes no sense, so there's simply no impl for it, and the compiler rejects it.

Order matters

coffee * 2 uses impl Mul<u32> for Money. Writing 2 * coffee would need a different impl, impl Mul<Money> for u32. Rust allows that one too (a local type appears in it), but it's a separate impl you'd have to write.

Default type parameters in your own traits

Defaults exist mainly for two reasons: to make the common case short (impl Add for Money), and to let a library add a type parameter to an existing trait without breaking every impl already written. You can use them in your own traits:

trait Similar<Other = Self> {
    fn similarity(&self, other: &Other) -> f64;
}

impl Similar for Song compares songs with songs; impl Similar<Playlist> for Song compares a song with a playlist.

Common mistakes

use std::ops::Add;

struct Meters(f64);

impl Add for Meters {
    fn add(self, other: Meters) -> Meters {
        Meters(self.0 + other.0)
    }
}

fn main() {}
error[E0046]: not all trait items implemented, missing: `Output`

Your task

Implement two operators for the game vector Vec2: Vec2 + Vec2 adds the xs and the ys, and Vec2 * i32 multiplies both coordinates by the number. Both return a Vec2.

Challenge 2

Times work like std::time::Instant and Duration: subtracting two points in time gives a length of time, and adding a length of time to a point gives a later point. Implement Timestamp - Timestamp (output Seconds, assume the left one is later) and Timestamp + Seconds (output Timestamp).

Challenge 3

Write your own trait with a default type parameter. Declare trait Distance<Other = Self> with one method fn distance(&self, other: &Other) -> u32;. Then implement it twice for the grid Cell:

Previous: Associated types versus generic parameters Next: Supertraits and fully qualified syntax