CleanCut/ultimate_rust_crash_course

Cannot get points while iterrating over Shots vector

marekpetak opened this issue · 2 comments

Hi, just doing the excersise G-COLLECTIONS_ENUMS and I'm stuck on the last point 3, looping through shots. I'm not really sure how to fix this.

here is my code:

// Silence some warnings that could distract from the exercise
#![allow(unused_variables, unused_mut, dead_code)]

// Someone is shooting arrows at a target.  We need to classify the shots.
//
// 1a. Create an enum called `Shot` with variants:
// - `Bullseye`
// - `Hit`, containing the distance from the center (an f64)
// - `Miss`
//
// You will need to complete 1b as well before you will be able to run this program successfully.
enum Shot {
    Bullseye,
    Hit(f64),
    Miss,
}

impl Shot {
    // Here is a method for the `Shot` enum you just defined.
    fn points(self) -> i32 {
        match self {
            Shot::Bullseye => 5,
            Shot::Hit(x) => {
                if x < 3.0 {
                    2
                } else {
                    1
                }
            },
            Shot::Miss => 0
        }
        // 1b. Implement this method to convert a Shot into points
        // - return 5 points if `self` is a `Shot::Bullseye`
        // - return 2 points if `self` is a `Shot::Hit(x)` where x < 3.0
        // - return 1 point if `self` is a `Shot::Hit(x)` where x >= 3.0
        // - return 0 points if `self` is a Miss
    }
}

fn main() {
    // Simulate shooting a bunch of arrows and gathering their coordinates on the target.
    let arrow_coords: Vec<Coord> = get_arrow_coords(5);
    let mut shots: Vec<Shot> = Vec::new();

    // 2. For each coord in arrow_coords:
    //
    //   A. Call `coord.print_description()`
    //   B. Create the correct variant of `Shot` depending on the value of
    //   `coord.distance_from_center()`
    //      - Less than 1.0 -- `Shot::Bullseye`
    //      - Between 1.0 and 5.0 -- `Shot::Hit(value)`
    //      - Greater than 5.0 -- `Shot::Miss`

    for coord in arrow_coords.iter() {
        coord.print_description();

        if coord.distance_from_center() > 1.0 {
            shots.push(Shot::Bullseye);
        } else if coord.distance_from_center() >= 1.0 && coord.distance_from_center() <= 5.0 {
            shots.push(Shot::Hit(coord.distance_from_center()))
        } else {
            shots.push(Shot::Miss);
        }
    }

    let mut total = 0;
    // 3. Finally, loop through each shot in shots and add its points to total
    
    for shot  in shots.iter() {
        total += shot.points();
    }

    println!("Final point total is: {}", total);
}

The error is pointing at line toal += shot.points() saying

move occurs because *shot has type Shot, which does not implement the Copy trait

I know what it means, but I don't know how to get it working. Thank you!

@marekpetak I'm so sorry I didn't see the notification for this issue! I've set up a filter to make sure I don't miss any future notifications.

The problem you have hit here is a combination of two factors:

  • The method .points() takes shot by value, which means that the method will consume shot and return something else.
  • Your for loop is borrowing shot by immutable reference using the .iter() method.

So, the ownership of shot cannot be transferred to the .points() method via an immutable reference borrowed by the for loop.

What we need to do in this case is to take ownership of each shot in the shots vector by consuming the vector in the for loop with for shot in shots (no .iter()). This way, each shot is removed from the vector and ownership of it is transferred to the for loop. This does have the side-effect of destroying the source vector. If we wanted to avoid destroying the source vector (so that we could re-use it later for something else), then one solution would be to refactor the .points() method to take a reference to a Shot (fn points(&self)) instead of taking ownership of the shot value.

The way I keep this straight is by remembering that every time I loop through things there are 3 ways general approaches:

  1. Consume a collection, which gives you ownership of each item, but has the side-effect of destroying the collection.
for myvar in mycollection
  1. Inspect a collection by temporarily borrowing an immutable reference to each item.
for myreference in mycollection.iter() {}
// or this, which desugars to the example above
for myreference in &mycollection {}
  1. Inspect and potentially mutate a collection by temporarily borrowing a mutable reference to each item.
for mymutref in mycollection.iter_mut()
// or this, which desugars to the example above
for mymutref in &mut mycollection

Thank you very much, I've managed to fix it.