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?