2

I would like to have a cache structure that would allow me to query for some values, and if they are not present, they would be created automatically. The following code snippet shows what I have in mind:

use std::collections::HashMap;

struct Cache {
  cache: HashMap<i32, Vec<i32>>,
}

impl Cache {

  fn new() -> Cache {
    Cache {
      cache: HashMap::new(),
    }
  }

  // If the key exists in the cache, quickly returns
  // the corredponding value. Otherwise, initializes
  // the value in the cache first, and then returns it.
  fn get(&mut self, key: i32) -> &Vec<i32> {
    let value = self.cache.get(&key);
    if value.is_some() {
      return value.unwrap();
    }
    self.cache.insert(key, Vec::new());
    self.cache.get(&key).unwrap()
  }

}

However, because of lifetime issues, this code does not compile:

error[E0502]: cannot borrow `self.cache` as mutable because it is also borrowed as immutable
  --> src/main.rs:23:5
   |
18 |   fn get(&mut self, key: i32) -> &Vec<i32> {
   |          - let's call the lifetime of this reference `'1`
19 |     let value = self.cache.get(&key);
   |                 -------------------- immutable borrow occurs here
20 |     if value.is_some() {
21 |       return value.unwrap();
   |              -------------- returning this value requires that `self.cache` is borrowed for `'1`
22 |     }
23 |     self.cache.insert(key, Vec::new());
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `borrow` due to previous error

The returned error does not make a lot of sense to me, since the immutable borrow in line 19 is no longer used in line 23, so it should not be in conflict with another borrow.

I found a virtually identical piece of code in the docs about lifetimes. Unfortunately, this doc only says "This will eventually get fixed." and does not give any hint how could I solve this issue myself.

I found a question on stack overflow that tries to solve a similar problem (link) and the proposed solution almost works for me:

use std::collections::HashMap;

struct Cache {
  cache: HashMap<i32, Vec<i32>>,
}

impl Cache {

  fn new() -> Cache {
    Cache {
      cache: HashMap::new(),
    }
  }

  // If the key exists in the cache, quickly returns
  // the corredponding value. Otherwise, initializes
  // the value in the cache first, and then returns it.
  fn get(&mut self, key: i32) -> &Vec<i32> {
    self.cache.entry(key).or_insert_with(|| Vec::new())
  }

}

This code compiles and works. However, it does not generalize to a more complex case where instead of a simple Vec::new() I would like to use some additional logic for constructing the default value:

use std::collections::HashMap;

struct Cache {
  cache: HashMap<i32, Vec<i32>>,
}

impl Cache {

  fn new() -> Cache {
    Cache {
      cache: HashMap::new(),
    }
  }

  // If the key exists in the cache, quickly returns
  // the corredponding value. Otherwise, initializes
  // the value in the cache first, and then returns it.
  fn get(&mut self, key: i32) -> &Vec<i32> {
    self.cache.entry(key).or_insert_with(|| self.get_default_value(key))
  }

  fn get_default_value(&mut self, key: i32) -> Vec<i32> {
    if key == 0 {
      return Vec::new();
    }
    let mut value = self.get(key / 2).clone();
    value.push(key);
    value
  }

}

The compiler will not allow me to call any method to generate the default value, because it would need to borrow self.

error[E0500]: closure requires unique access to `*self` but it is already borrowed
  --> src/main.rs:19:42
   |
18 |   fn get(&mut self, key: i32) -> &Vec<i32> {
   |          - let's call the lifetime of this reference `'1`
19 |     self.cache.entry(key).or_insert_with(|| self.get_default_value(key))
   |     -------------------------------------^^-----------------------------
   |     |                                    |  |
   |     |                                    |  second borrow occurs due to use of `*self` in closure
   |     |                                    closure construction occurs here
   |     borrow occurs here
   |     returning this value requires that `self.cache` is borrowed for `'1`

For more information about this error, try `rustc --explain E0500`.
error: could not compile `borrow` due to previous error

Do you know any workaround that would allow me to implement the get method?

mst
  • 21
  • 1

0 Answers0