We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership I: Moves, Copy, Clone, Drop step 5 of 20
Copy: the types that don't move
Translate a list of points and report their centroid — using a Point that
does not move.
pub fn centroid_path(points: Vec<Point>, dx: i64, dy: i64) -> Vec<Point>
The first question every beginner asks
Why does
i32behave differently fromString?
let a = 5;
let b = a;
println!("{a}"); // fine
let s = String::from("hi");
let t = s;
println!("{s}"); // E0382
The honest answer is that one bit of trait information changes whether the
source is invalidated. If a type implements Copy, assignment duplicates and
both names stay valid. If it does not, assignment moves and the source dies.
That is the entire rule. There is no special case for integers, no list of
“primitive” types the compiler knows about — i32 is Copy because the
standard library says impl Copy for i32, and your own types can say the same
thing.
The condition for being allowed to say it is: every field is Copy, and the
type has no destructor. Which makes sense. Copy means “duplicating the bytes
produces a second, fully independent value”, and a String field breaks that —
duplicating its three words gives you two owners of one heap buffer.
Two facts to bank now, because they will save you weeks in the next track:
-
&Tis alwaysCopy. Shared references duplicate freely; that is what makes them ergonomic. -
&mut Tis neverCopy. Exclusive means exclusive. A&mutthat could be duplicated would not be exclusive, so passing one into a function moves it.
The task
The grader builds Point { x, y } values from JSON and reads .x / .y back
out of whatever you return, so keep the type name and both field names exactly
as the starter has them.
Return a Vec<Point> containing:
-
every input point, translated by
(dx, dy), in order; then -
one final point: the centroid of the original (untranslated) points,
computed as the sum of the coordinates divided by the number of points using
ordinary
i64division. If there are no points, the final point is the origin(0, 0)and it is the only element.
Two fixed helpers, both taking a Point by value:
fn shifted(p: Point, dx: i64, dy: i64) -> Point
fn accumulate(total: Point, p: Point) -> Point
Each point therefore has to reach both. Under the starter’s derive that is E0382. Under the right derive it is free.
The compile-time check
The starter contains this. Do not delete it:
const fn assert_copy<T: Copy>() {}
const _: () = assert_copy::<Point>();
It does nothing at runtime. It exists so that “make Point Copy“ is a
requirement the compiler enforces rather than a suggestion you could dodge by
sprinkling .clone() around. If Point is not Copy you get:
error[E0277]: the trait bound `Point: Copy` is not satisfied
help: consider annotating `Point` with `#[derive(Copy)]`
Static assertions like this are a real technique, not a teaching prop —
const items are type-checked, so any trait bound you can write is a bound you
can assert at compile time for zero cost.
::: question #[derive(Copy)] on its own does not compile. Why, and what else can go wrong with these derives?
Copy has Clone as a supertrait: pub trait Copy: Clone {}. A type
cannot be Copy without also being Clone, so you always write
#[derive(Clone, Copy)]. (For a Copy type, the derived Clone compiles down
to a plain byte copy — there is no cost to having both.)
The other failure is E0204. Try adding a String field to Point and the
derive is rejected:
error[E0204]: the trait `Copy` cannot be implemented for this type;
field `label` does not implement `Copy`
That is the rule stated as a diagnostic. There is also a third exclusion —
a type with a Drop impl can never be Copy — which gets its own article
later in this track, because the reasoning behind it is more interesting than
it first looks.
:::
Don’t reach for clone
Once Point is Copy, writing p.clone() is not a mistake the compiler
catches — it compiles and does the right thing. Clippy catches it:
error: using `clone` on type `Point` which implements the `Copy` trait
help: try dereferencing it: `p`
clone_on_copy is one of the very few ownership lints that is on by default,
and it is a genuinely useful gate: it tells you, mechanically, that you reached
for a tool you did not need. Take the hint.
One design note: resist making this a generic Point<T>. #[derive(Copy)] on a
generic struct silently adds a T: Copy bound, which produces a type that is
Copy for some T and not others, and debugging that is a lesson for a much
later track.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.