Borrow checker and understanding it

This is a highly contrived example just for my understanding of the mechanics of the borrow checker.
My questions are the following:

(1) Why should the immutable borrow affect since it is clearly "scoped"? Line 13

(2) How am I using an immutable borrow in Line 23?

(3) This is an exclusive mutable borrow since the previous immutable borrow is scoped. What am I missing here?

Here is the link to the code and pasted below too. It does not compile of course and here is the error that it prints:

Compiling playground v0.0.1 (/playground)
error[E0502]: cannot borrow `map` as mutable because it is also borrowed as immutable
  --> src/main.rs:26:17
   |
13 |         let ks = map.keys();
   |                  ---------- immutable borrow occurs here
...
23 |         match iter.next() {
   |               ----------- immutable borrow later used here
...
26 |                 map.remove(&key);
   |                 ^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `playground` due to previous error
use std::collections::HashMap;
use std::vec::Vec;

fn main() -> ()
{
    let mut map : HashMap<String, String> = HashMap::new();
    map.insert("Bob".into(), "Marley".into());
    map.insert("Jack".into(), "Ryan".into());
    map.insert("Bill".into(), "Tilden".into());
    
    let mut keys : Vec<&str> = Vec::new(); 
    {
        let ks = map.keys();
        for key in ks
        {
            keys.push(key);
        }
    }
    
    let mut iter = keys.iter();
    loop
    {
        match iter.next() {
            Some(key) => {
                let key : String = (*key).into();
                map.remove(&key);
            }
            None => break
        }
    }
    
    println!("len {}", map.len());
}

keys contains strings borrowed from map so map is considered borrowed for as long as the keys Vec exists.

When you remove the item from map the key string is deallocated. If you were allowed to do that with the keys still borrowed in the keys Vec, you would be allowed to read a key and access memory that no longer belongs to you.

A "borrow" is not only when you've got a literal &. Since references are values too, they are usable (readable, movable, etc.) just like every other value. Therefore, a borrow is active as long as the reference resulting from it is kept around. (I don't think there is any useful semantic difference between "a borrow" and "a reference".)

You need to convert keys to an independent self-owning type String before you push them into keys (use Vec<String>).

Otherwise removal of the first key could (at least as far as the borrow checker knows) invalidate all other keys too, making the keys vec unsafe to use.


Another way to look at it is that &mut strictly exclusive access. When &mut of something exists, it must be a guarantee that no other reference to the same data can exist at the same time. &mut map gives exclusive access to all of map, including its keys. The keys existing in keys Vec create a paradox, because &mut map is not the only way to access keys it has.

Just because you created a borrow in a lexical scope doesn't mean that borrow is limited to said scope. (If value liveness scope limited lifetimes, you could never have a local s: &'static str = "", for example.)

Let's call the lifetime of the references in keys at line 11 'x. The lifetime must last at least through the loop where the values are used (say until line 30).

How is keys populated? The values are borrowed from map. So the borrow via map.keys() on line 13 must be at least as long as 'x -- it too must last until line 30. That's a shared (immutable) borrow of map.

This is the borrow that conflicts on line 26. It's also the one being called "used" at line 23, because the iteration of keys is returning values you got out of the borrow on line 13.

1 Like

Perfect. Thanks. Sincerely appreciated.