We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Generics and Traits step 15 of 24
Operator overloading: + is just a trait method
Rust’s operators are not magic and they are not built into the type system. Each one is sugar for a trait method:
| you write | compiler calls |
|---|---|
a + b |
Add::add(a, b) |
a - b |
Sub::sub(a, b) |
-a |
Neg::neg(a) |
a[i] |
*Index::index(&a, i) |
That is the whole mechanism. i32 + i32 works because std implements Add
for i32, not because the compiler knows about integers.
The surprise
struct Meters(f64);
Meters(3.0) + Meters(1.0)
error[E0369]: cannot add `Meters` to `Meters`
|
= note: an implementation of `Add` might be missing for `Meters`
Operators are not inherited from the wrapped type. A newtype around f64
gets nothing from that f64 — not +, not -, not comparison. Everyone
hits this exactly once, and it is one of the clearest demonstrations that
Rust’s traits are opt-in all the way down. It is also the point: your
Meters type refuses to be added to a Seconds by default, and that is the
behaviour you wanted when you made the newtype.
Your task
#[derive(Clone, Copy)]
pub struct Vec2 { pub x: f64, pub y: f64 }
impl Add for Vec2 { ... }
impl Sub for Vec2 { ... }
impl Neg for Vec2 { ... }
impl Index<usize> for Vec2 { ... }
pub fn apply(ops: Vec<(String, f64, f64)>) -> Vec<(f64, f64)>
apply runs an accumulator starting at (0.0, 0.0) and emits one pair per
operation:
| op | effect | emits |
|---|---|---|
add |
state = state + (x, y) |
the new state |
sub |
state = state - (x, y) |
the new state |
neg |
state = -state |
the new state |
index |
— |
(state[0], state[1]) |
| other | — |
(0.0, 0.0) |
Sub, Neg and Index are already written. Add is missing, and the
starter therefore fails with the E0369 above. Adding it is the exercise; the
rest of the file is there for you to read.
Three details in those impl blocks
type Output is an associated type. Add declares
type Output; and each impl decides what it is. For Vec2 + Vec2 the output
is a Vec2, but nothing requires that: std implements
Mul<f64> for Duration with Output = Duration, and Sub for Instant with
Output = Duration. The trait has a shape; the impl fills in the types.
Add has a default type parameter. The real declaration is
trait Add<Rhs = Self>. Writing impl Add for Vec2 means
impl Add<Vec2> for Vec2. To support Vec2 * f64 you would write
impl Mul<f64> for Vec2 — the parameter is what lets one type be addable to
several others.
Index returns a reference. The signature is
fn index(&self, i: usize) -> &Self::Output, not -> Self::Output. Return
the value directly and you get E0308. The reference is what makes
v[0] = 1.0 possible for types that also implement IndexMut, and it is why
indexing can hand out a borrow of something inside a collection without
copying it.
Two clippy gates worth knowing before you trip them
clippy::suspicious_arithmetic_impl is default-on and catches the
copy-paste bug you are about to make:
impl Add for Vec2 {
fn add(self, rhs: Vec2) -> Vec2 {
Vec2 { x: self.x - rhs.x, ... } // rejected: `-` inside an `Add` impl
}
}
Almost every real instance of this is a duplicated Sub block someone forgot
to finish editing. Its sibling suspicious_op_assign_impl covers +=.
clippy::should_implement_trait is the other half. Define an inherent
fn add(self, other: Vec2) on a public type instead of implementing the trait
and clippy stops you: “defining a method called add on this type; consider
implementing the std::ops::Add trait”. If you named it add, callers will
reach for +; give them +.
One design note for later
Add::add takes self by value. That is fine here because Vec2 is
Copy — 16 bytes, no allocation. For a type that owns a heap buffer, a + b
would consume both operands, which is usually not what a caller wants. The
standard answers are to implement Add for &T as well
(impl Add<&Matrix> for &Matrix) or to make the value-taking impl clone
internally. std does the former for String, which is why
s1 + &s2 consumes s1 but only borrows s2.
Remember the grade is compile + tests + clippy -D warnings.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.