Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does HashMap::get_mut() take ownership of the map for the rest of the scope?

Tags:

rust

I have the following code that inserts some values into a HashMap and then gets them back out:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    let mut world = map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

Attempting to compile this code gives the following error:

error[E0499]: cannot borrow `map` as mutable more than once at a time
  --> src/main.rs:10:21
   |
9  |     let mut world = map.get_mut(&4);
   |                     --- first mutable borrow occurs here
10 |     let mut hello = map.get_mut(&1);
   |                     ^^^ second mutable borrow occurs here
11 | }
   | - first borrow ends here

After perusing the API docs for both the remove() and get_mut() methods (fortunately they are pretty close to each other!) there is nothing that stands out to me from the method signatures why the remove() method does not mutably borrow the map for the rest of the current scope while the get_mut() method does.

The other piece of data that I have that also mystifies me is that this code compiles:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

Not storing the result of the first call to get_mut() doesn't cause the map to be mutably borrowed for the rest of the scope? How could I have known this from looking at the documentation? Am I missing something else?

like image 482
Ian Andrews Avatar asked Sep 24 '15 12:09

Ian Andrews


1 Answers

This error is a limitation of the implementation of the borrow checker before non-lexical lifetimes. With those enabled, the original code will work as-is:

use std::collections::HashMap;

fn things() {
    let mut map = HashMap::new();
    map.insert(5, "thing");
    map.insert(4, "world");
    map.insert(1, "hello");
    let mut thing = map.remove(&5);
    let mut world = map.get_mut(&4);
    let mut hello = map.get_mut(&1);
}

fn main() {}

This is because the compiler is smarter and can see that you aren't using world anymore by the time you get to map.get_mut(&1), so it doesn't need to have a valid reference anymore.

You can get equivalent code in previous versions of Rust by adding an explicit scope:

let mut thing = map.remove(&5);
{
    let mut world = map.get_mut(&4);
}
let mut hello = map.get_mut(&1);

Why does HashMap::get_mut() take ownership of the map

It absolutely does not do that. Ownership is a precise term in Rust code. Note that the error message specifically says

previous borrow of map occurs here

A borrow is not ownership. If I borrow your car, I don't own your car.

Your real question is "why does it borrow it for the rest of the scope". Let's look at the signature:

fn get_mut<Q: ?Sized>(&mut self, k: &Q) -> Option<&mut V> 
where
    K: Borrow<Q>,
    Q: Hash + Eq,

In words, this could be read as

Given a mutable reference to a HashMap (&mut self) and something that can be used to find the key (K: Borrow<Q>, Q: Hash + Eq), return a mutable reference to the value if one matches (Option<&mut V>)

However, that returned mutable reference will be changing something in the HashMap, that's why it's a mutable reference at all. You are only allowed to have multiple immutable borrows OR one mutable borrow at a time. This prevents writing code that causes inconsistencies and safety issues.

Let's look at remove:

fn remove<Q: ?Sized>(&mut self, k: &Q) -> Option<V> 
where
    K: Borrow<Q>, 
    Q: Hash + Eq,

This returns an owned value, not a reference into the HashMap. Once the method is done, the borrow of the map is over.

like image 174
Shepmaster Avatar answered Sep 21 '22 05:09

Shepmaster