A mutable borrow inside immutable borrow


#1

Btw, correct me if I’m wrong in saying “A mutable borrow inside immutable borrow”.
I have this code.

use std::collections::HashMap;

#[cfg(test)]
mod test {
    use super::Cacher;

    #[test]
    fn normal_usage() {
        use std::thread;
        use std::time::Duration;
        
        let mut cached_caller = Cacher::new (| arg : &str | {
            println!("calculating slowly...");
            thread::sleep(Duration::from_secs(2));
            String::from(arg).len()
        });

        assert_eq!(cached_caller.value("32"), 2);
        assert_eq!(cached_caller.value("agath"), 5);
        assert_eq!(cached_caller.value("32"), 2);
        assert_eq!(cached_caller.value("agath"), 5);
    }
}

pub struct Cacher<T, G, H>
    where H: Clone, T: Fn(G) -> H, 
{
    mapping: HashMap<G, H>,
    calculation: T,
}

impl<T, G, H> Cacher<T, G, H>
    where H: Clone, T: Fn(G) -> H, G: std::cmp::Eq, G: std::hash::Hash, G: Clone {
    pub fn new(calculation: T) -> Cacher<T, G, H> {
        Cacher {
            mapping: HashMap::new(),
            calculation: calculation,
        }
    }

    pub fn value(&mut self, arg: G) -> H {
        let res = match self.mapping.get(&arg) {
            Some(v) => Some(v.clone()),
            None => None,
        };

        match res {
             Some(v) => v,
             None => {
                let arg_clone = arg.clone();
                let v = (self.calculation)(arg);
                let clone = v.clone();
                self.mapping.insert(arg_clone, clone);
                v
            }
        }
    }
}

Can I not do something like this directly?

    pub fn value(&mut self, arg: G) -> H {
        // let res = match self.mapping.get(&arg) {
        //     Some(v) => Some(v.clone()),
        //     None => None,
        // };

        match self.mapping.get(&arg) {
             Some(v) => v.clone(),
             None => {
                let arg_clone = arg.clone();
                let v = (self.calculation)(arg);
                let clone = v.clone();
                self.mapping.insert(arg_clone, clone);
                v
            }
        }
    }

This gives the error:

   Compiling closures v0.1.0 (file:///home/harshithg/dev/rust/closures)
error[E0502]: cannot borrow `self.mapping` as mutable because it is also borrowed as immutable
  --> src/lib.rs:53:17
   |
47 |         match self.mapping.get(&arg) {
   |               ------------ immutable borrow occurs here
...
53 |                 self.mapping.insert(arg_clone, clone);
   |                 ^^^^^^^^^^^^ mutable borrow occurs here
...
56 |         }
   |         - immutable borrow ends here

error: aborting due to previous error

error: Could not compile `closures`.

Thank you


#2

This is a limitation of the current borrow checker. self.mapping is borrowed for the duration of the match. There is work done to make this possible (non lexical lifetimes (nll)).


#3

You can

match self.mapping.get(&arg).clone()

#4

A (not very nice) work-around is to do this also

let mut should_insert = false;
 
match self.mapping.get(&arg) {
	should_insert = true;
}

if should_insert {
	self.mapping.insert(arg_clone, clone);
}

#5

This didn’t work for me. Did it work for you?

This is what I did.
https://play.rust-lang.org/?gist=8d3176e06cc5273d0481fa406fb34833&version=undefined


#6

Yeah, this is a workaround, but I wanted to do it the Rust way. :stuck_out_tongue:
I looks un-clean.


#7

There is entry as alternative.


#8

In fact, you can see that it works with NLL: https://play.rust-lang.org/?gist=1fa42f58d0f3624057314d3f04f732b8&version=nightly