Skip to content

← Ownership I: Moves, Copy, Clone, Drop step 5 of 20

Easy Primitives

Copy values: translate points and find their centroid

Translate each input point, then append the centroid of the original points. Make Point Copy so the same point can be passed by value to two helpers.

pub fn centroid_path(points: Vec<Point>, dx: i64, dy: i64) -> Vec<Point>

Output rules

Keep the public Point type and its public x: i64 and y: i64 fields. Return a vector containing:

  1. Each input point translated to (x + dx, y + dy), in input order.
  2. One final point containing the centroid of the original, untranslated points.

Compute each centroid coordinate by summing that coordinate and dividing by the number of input points. Use integer division, which truncates toward zero: a total of -3 across two points gives -1, not -2.

For an empty input, return just Point { x: 0, y: 0 }, regardless of the translation. For one point, return its translated value followed by its original value. The output always has one more element than the input.

For example, points (1, 2) and (3, 4) with translation (10, 20) produce (11, 22), (13, 24), then centroid (2, 3).

Assume every translated coordinate and every running coordinate sum fits in i64, and the input length fits in i64. Keep calculations in integers so large coordinates remain exact. Overflow behavior is outside this exercise's input domain.

Why the starter cannot reuse a point

The two supplied helpers take Point by value:

  • accumulate(total, p) adds a point to the running total.
  • shifted(p, dx, dy) returns a translated point.

Keep these helpers and their signatures. The starter derives Clone but not Copy. Passing p to accumulate therefore moves it; a later use in shifted would produce E0382. Deriving Copy as well lets both calls receive a copy while leaving the local p usable.

Clone alone does not change assignment or argument passing. It provides an explicit method. Copy changes those implicit by-value uses and has Clone as a supertrait, so a Copy type must also implement Clone. Deriving both is the straightforward choice here; handwritten implementations are also possible.

Here is a complete example with a different small value type:

#[derive(Clone, Copy)]
struct Dimensions { width: u32, height: u32 }

fn area(d: Dimensions) -> u64 {
    u64::from(d.width) * u64::from(d.height)
}

fn main() {
    let size = Dimensions { width: 3, height: 4 };
    assert_eq!(area(size), 12);
    assert_eq!(size.width, 3);
}

The compile-time assertion

Retain the starter's assert_copy function and its const invocation. The T: Copy bound requires Point to implement Copy; otherwise compilation fails with E0277. The const invocation performs no runtime work.

This assertion is in the editable starter. Keeping it is an exercise requirement; the output harness does not independently prevent its removal or prove that your solution uses the supplied helpers.

With the assertion present, the compiler may report the missing Copy bound before the moved-value error. Adding Copy resolves both causes.

When a type can be Copy

A struct can implement Copy only if all its fields are Copy and it has no Drop implementation. Both Point fields are i64, so it is eligible. Eligibility alone does not implement the trait.

Adding a String field would make the Copy implementation invalid (E0204). Copying a shared reference, however, is allowed even when the referenced type is not Copy: it duplicates access to the same value, not the value itself. A mutable reference is not Copy, though calls can temporarily reborrow one.

Copy does not guarantee zero work or a particular machine instruction. Large Copy values can be expensive to duplicate. For this small Point, it gives the intended value semantics without an explicit clone or borrow.

Once Point is Copy, use p directly. Calling p.clone() is legal Rust but triggers Clippy's clone_on_copy lint. Submissions must compile, pass the output tests and pass Clippy with warnings treated as errors.