2

As part of the Rust book, I implemented a Cacher struct with generics. Cacher contains two fields: a closure calculation and a value field which is a HashMap:

use std::{collections::HashMap, hash::Hash};

struct Cacher<T, U, V>
where
    T: Fn(U) -> V,
    U: Eq + Hash,
{
    calculation: T,
    value: HashMap<U, V>,
}

impl<T, U, V> Cacher<T, U, V>
where
    T: Fn(U) -> V,
    U: Eq + Hash,
{
    fn new(calculation: T) -> Cacher<T, U, V> {
        Cacher {
            calculation,
            value: HashMap::new(),
        }
    }

    fn value(&mut self, arg: U) -> &V {
        self.value
            .entry(arg)
            .or_insert_with(|| (self.calculation)(arg))
    }
}

I'm struggling with the Cacher::value method which should do the following:

  • If the value HashMap contains a key matching the provided argument, then return the corresponding value.

  • If the value HashMap does not contain a key matching the argument, execute Cacher's calculation closure. Save the result of this closure in the HashMap, using the provided argument as the key.

I've tried a variety of solutions that always result in conflicts between mutable borrows and immutable borrows. I'm not sure how to resolve the issue.

error[E0502]: cannot borrow `self` as immutable because it is also borrowed as mutable
  --> src/lib.rs:27:29
   |
24 |       fn value(&mut self, arg: U) -> &V {
   |                - let's call the lifetime of this reference `'1`
25 |           self.value
   |           ----------
   |           |
   |  _________mutable borrow occurs here
   | |
26 | |             .entry(arg)
27 | |             .or_insert_with(|| (self.calculation)(arg))
   | |_____________________________^^__----__________________- returning this value requires that `self.value` is borrowed for `'1`
   |                               |   |
   |                               |   second borrow occurs due to use of `self` in closure
   |                               immutable borrow occurs here

error[E0382]: use of moved value: `arg`
  --> src/lib.rs:27:29
   |
12 | impl<T, U, V> Cacher<T, U, V>
   |         - consider adding a `Copy` constraint to this type argument
...
24 |     fn value(&mut self, arg: U) -> &V {
   |                         --- move occurs because `arg` has type `U`, which does not implement the `Copy` trait
25 |         self.value
26 |             .entry(arg)
   |                    --- value moved here
27 |             .or_insert_with(|| (self.calculation)(arg))
   |                             ^^                    --- use occurs due to use in closure
   |                             |
   |                             value used here after move

I'm running into a compile-time error because self.value.entry().or_insert_with() is a mutable borrow, while the use of self in the closure inside of or_insert_with() triggers an immutable borrow.

I'm also running into an error in the value method because I'm passing ownership of arg into entry() and trying to use it again inside of the closure. Should I be using references in this method? If so, how?

I understand the issues from a high level, but I'm struggling trying to find a fix around it. What can I do to prevent a mix of mutable and immutable borrows when trying to read or write to the value HashMap?

Solution

The solution I settled on.

The closure signature changed to Fn(&U) -> V and the value method changed to:

fn value(&mut self, arg: U) -> &V {
    match self.value.entry(arg) {
        Entry::Occupied(e) => e.into_mut(),
        Entry::Vacant(e) => {
            let v = (self.calculation)(e.key());
            e.insert(v)
        }
    }
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Pramod Jacob
  • 101
  • 4
  • Can you share your error? It may be because of your compiler version, but when I compile your code, the error is not a borrow conflict - it's instead because you pass `arg` as an owned value into `entry`, and then try to reuse it in the closure of `or_insert_with`. In fact, there's no way to do what you want with your current types - inserting the key requires ownership of the key, and the calculation requires ownership of the key. – Isaac van Bakel May 18 '19 at 23:41
  • I see both the error you mentioned and the error mentioned in my post. Should I be utilizing a reference to the arg param in the value method, rather than have the value method take ownership of "arg"? – Pramod Jacob May 19 '19 at 04:07
  • You can't do that - the call to `entry` takes ownership of the argument, so you can't reference it afterwards. If you try to get the reference from the entry using `Entry::key`, you'll have a borrow which will stop you from using `Entry::or_insert_with`, since that requires ownership. – Isaac van Bakel May 19 '19 at 09:02
  • 1
    [The duplicates applied to your question](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54ea24f045d40a6b41d5830a8e4d5e82) (and yes, I'd use a reference) – Shepmaster May 19 '19 at 14:01
  • I apologize that it's a duplicate question - I wasn't quite sure how to phrase it, so I wasn't sure where I could find that it was already answered. Thank you for taking the time to answer the question @Shepmaster! – Pramod Jacob May 19 '19 at 17:39
  • @PramodJacob no need to apologize for duplicates. Now that you’ve asked yours, anyone who uses the same terms will find this and be redirected to the original questions. – Shepmaster May 19 '19 at 17:47

0 Answers0