1

I have a block of code

let mut current_room = None;

for (ref room_id, ref poker_room) in &self.planning_pokers {
    if poker_room.has_player(user_id.clone()) {
        current_room = Some(room_id.clone());
        break
    }
}

match current_room {
    Some(room_id) => {
        self.planning_pokers.get_mut(&room_id.clone()).unwrap().remove_player(user_id.clone());
        if self.planning_pokers.is_empty() {
            self.planning_pokers.remove(&room_id.clone());
        }
    },
    None => (),
    _ => ()
}

println!("Player {} joined room {}", join_room.room_id.clone(), user_id.clone());
if self.planning_pokers.contains_key(&join_room.room_id.clone()) {

}

This fails to compile due to a mutable and immutable borrow conflict. The conflict in question pertains to me setting the value of current_room to Some(room_id.clone()). If I instead do Some(room_id.clone().to_string()) everything works as expected. Why is this the case? And why does the rust compiler give me an error that is seemingly unrelated?

For reference this is the error:

cannot borrow self.planning_pokers as mutable because it is also borrowed as immutable

peterdn
  • 2,386
  • 1
  • 23
  • 24
Daniel
  • 589
  • 4
  • 19

1 Answers1

2

It looks like room_id is a double reference of type &&T. This means that in Some(room_id.clone()), you are cloning a borrowed reference to data that self.planning_pokers owns, rather than cloning the underlying data itself. Because you then assign it to the variable current_room in the outer scope, self.planning_pokers remains borrowed throughout. It might help to see that in this case, current_room is of type Option<&T>.

Some(room_id.to_string()) instead creates a copy of the underlying data which is moved into current_room. In this case current_room is of type Option<T>.

The difference is (I believe) due to Rust's auto-dereferencing behaviour. Immutable references implement Clone, and so Rust calls the method on &T. However, they do not implement ToString, so Rust dereferences all the way down to T.


Note: rust-clippy warns about this very issue. From the wiki:

clone_double_ref

What it does: Checks for usage of .clone() on an &&T.

Why is this bad? Cloning an &&T copies the inner &T, instead of cloning the underlying T.

Example:

fn main() {
   let x = vec![1];
   let y = &&x;
   let z = y.clone();
   println!("{:p} {:p}",*y, z); // prints out the same pointer
}
peterdn
  • 2,386
  • 1
  • 23
  • 24