Question about Vec last and shared references

Hello everyone,

Sorry if this is too basic a question; I'm trying to learn rust and maybe I've hit upon something trivial.

I get this error message for the code below:

error[E0507]: cannot move out of `pet` as enum variant `Cat` which is behind a shared reference

I don't see whose ownership I'm transferring out of "pet", and who else is sharing ownership (of that unknown thing). With some trial-and-error, I realized that it has something to do with the "last" method of Vec, but I couldn't get any more clues beyond that.

Thank you.

use Pet::*;


fn main() {
    let mut pets : Vec<Pet> = vec![];
    pets.push(Cat(PetInfo{name: "tom".to_string()}));
    
    let pet = pets.last().unwrap();
    match pet {
        Cat(mut petinfo) => {
            petinfo.change_name("tommy".to_string());
        }
        _ => ()
    };
    
}

enum Pet {
    Cat(PetInfo),
    Dog(PetInfo)
}

struct PetInfo {
    name: String
}
impl PetInfo {
    fn change_name(&mut self, new_name: String) {
        self.name = new_name;
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0507]: cannot move out of `pet` as enum variant `Cat` which is behind a shared reference
  --> src/main.rs:9:11
   |
9  |     match pet {
   |           ^^^
10 |         Cat(mut petinfo) => {
   |             -----------
   |             |
   |             data moved here
   |             move occurs because `petinfo` has type `PetInfo`, which does not implement the `Copy` trait

For more information about this error, try `rustc --explain E0507`.
error: could not compile `playground` due to previous error

The issue here is that last() returns a (shared) reference to the last element of the vector. That is, pet is of type &Pet, not Pet, so you don't have ownership of it. That means you can't change it (because shared references are immutable) or move out of it (because that would leave an invalid element in the vector). Unfortunately, the match there destructures pet moving the petinfo out of it.

Your options here would be to either remove the last element from the vector with pop(), then you can do what you want with it but it would no longer be in the vector (unless you push it back in afterwards), or get a unique reference that allows for mutating in place with last_mut().

Here's an example with last_mut():

use Pet::*;

fn main() {
    let mut pets: Vec<Pet> = vec![];
    pets.push(Cat(PetInfo {
        name: "tom".to_string(),
    }));

    let pet = pets.last_mut().unwrap();
    match pet {
        Cat(petinfo) => {
            petinfo.change_name("tommy".to_string());
        }
        _ => (),
    };

    println!("{:?}", pets);
}

#[derive(Debug)]
enum Pet {
    Cat(PetInfo),
    Dog(PetInfo),
}

#[derive(Debug)]
struct PetInfo {
    name: String,
}
impl PetInfo {
    fn change_name(&mut self, new_name: String) {
        self.name = new_name;
    }
}

(playground)

3 Likes

Change that to:

let pet = pets.last_mut().unwrap();

must be

Cat(ref mut petinfo) => { ... }

You need to add the ref in order not to move out of petinfo.

Playground.

2 Likes

Thanks a lot! ref is a new thing I learnt; will read up on it

As @jameseb7 solutions points out, you can actually omit the ref mut part completely when using last_mut()

ref and ref mut are used to take references when matching against a pattern. The reason you can omit them is due to something called "match ergonomics" that was added to make writing match expressions easier, where it will assume you want a reference out if you give a reference in. Adding ref or mut to your match explicitly disables match ergonomics.

2 Likes

Thank you, that was very clear. I understood it.