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
- Forgetting the output type:
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`
- Using a combination you didn't implement: with only
Mul<u32> for Money,coffee * coffeefails withmismatched types(expectedu32, foundMoney). - Surprising operators: overload an operator only when its meaning is obvious to a reader.
+on money is fine;+that sends an email is not.
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:
impl Distance for Cell(using the default): the Manhattan distance,|row1 - row2| + |col1 - col2|.impl Distance<Row> for Cell: how many rows the cell is away from that row.
Previous: Associated types versus generic parameters Next: Supertraits and fully qualified syntax