0
use std::collections::HashMap;


#[derive(Clone, Hash, Eq, PartialEq)]
struct HeavyCloneKey;

struct Map<V> {
    map : HashMap<HeavyCloneKey, V>
}

impl<V> Map<V> {
    pub fn get_mut(&mut self, key: &HeavyCloneKey) -> Option<&mut V> {
        if let Some(v) = self.map.get_mut(key) {
            return Some(v);
        }
        
        // some logic to decide if create a new value;
        if self.map.len() > 64 {
            None
        } else {
            let v = create_v_with_long_code(); 
            let v = self.map.entry(key.clone()).or_insert_with(|| v);
            Some(v)
        }
    }
}

fn create_v_with_long_code<V>() -> V {
    unimplemented!()
}

fn main() {
    
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=19c45ebfb1a39271ff5bd2443399596a

I can't understand why this can't work:

    error[E0502]: cannot borrow `self.map` as immutable because it is also borrowed as mutable
  --> src/main.rs:18:12
   |
12 |     pub fn get_mut(&mut self, key: &HeavyCloneKey) -> Option<&mut V> {
   |                    - let's call the lifetime of this reference `'1`
13 |         if let Some(v) = self.map.get_mut(key) {
   |                          -------- mutable borrow occurs here
14 |             return Some(v);
   |                    ------- returning this value requires that `self.map` is borrowed for `'1`
...
18 |         if self.map.len() > 64 {
   |            ^^^^^^^^ immutable borrow occurs here

These two borrows won't appear at the same time, what's the problem? How can I do this?

Although the code can be compiled by using contains_key before get_mut, it pays more overhead;

  • @shepmaster might be worth having a more prominent link to Polonius' progress and a clearer note that even with NLL it still won't work until Polonius lands in the linked answer, as the NLL RFC does specifically mention this pattern yet it still doesn't work despite NLL being merged. – Masklinn Apr 27 '21 at 06:01
  • You can use unsafe to solve this without the second lookup: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=af2b11fc9bd724db8b2c0f34a7bf5135 – user4815162342 Apr 27 '21 at 07:03

1 Answers1

1

It helps a little to desugar the lifetimes by providing your own annotations on the references. It doesn't really matter here though as lifetimes aren't actually the issue.

pub fn get_mut<'s, 'k>(&'s mut self, key: &'k HeavyCloneKey) -> Option<&'s mut V> {

This gets us a little better error message:

error[E0502]: cannot borrow `self.map` as immutable because it is also borrowed as mutable
  --> src/main.rs:18:12
   |
12 |     pub fn get_mut<'s, 'k>(&'s mut self, key: &'k HeavyCloneKey) -> Option<&'s mut V> {
   |                    -- lifetime `'s` defined here
13 |         if let Some(v) = self.map.get_mut(key) {
   |                          -------- mutable borrow occurs here
14 |             return Some(v);
   |                    ------- returning this value requires that `self.map` is borrowed for `'s`
...
18 |         if self.map.len() > 64 {
   |            ^^^^^^^^ immutable borrow occurs here

error[E0499]: cannot borrow `self.map` as mutable more than once at a time
  --> src/main.rs:22:21
   |
12 |     pub fn get_mut<'s, 'k>(&'s mut self, key: &'k HeavyCloneKey) -> Option<&'s mut V> {
   |                    -- lifetime `'s` defined here
13 |         if let Some(v) = self.map.get_mut(key) {
   |                          -------- first mutable borrow occurs here
14 |             return Some(v);
   |                    ------- returning this value requires that `self.map` is borrowed for `'s`
...
22 |             let v = self.map.entry(key.clone()).or_insert_with(|| v);
   |                     ^^^^^^^^ second mutable borrow occurs here

We have two errors here. Both stem from the rust compiler not being able to tell that we don't use the mutable borrow from line 13 later in the function.

The first says "you grabbed a mutable reference to self here so we can't let you have an immutable reference there". The second says "you grabbed a mutable reference to self here so we can't let you have a second mutable reference there".

How do we fix this? By making it clearer to the compiler that we'll never hold that first mutable reference. We can do this by manually checking if that key exists first. Change this

        if let Some(v) = self.map.get_mut(key) {
            return Some(v);
        }

to this

        if self.map.contains_key(key) {
            return self.map.get_mut(key);
        }

and it will compile.

PitaJ
  • 12,969
  • 6
  • 36
  • 55
  • The proposed fix requires two separate hash table lookups, which might not be acceptable for code that cares about performance. – user4815162342 Apr 26 '21 at 17:08
  • That's true. Ideally the compiler could optimize out one of those lookups, but that is not gauranteed. – PitaJ Apr 26 '21 at 21:00
  • Thanks for your suggestion. Although it can compile, it would need two hashes and two table lookups each time. The problem is, there really no multi-borrows risks, but we have to assume there is, and pay extra runtime overhead; – chenqiang min Apr 27 '21 at 00:56