I\'m trying to implement a lazy-construction / memoized evaluation / caching idiom in Rust.
There\'s an outer type which has a bunch of data and an accessor method.
So, the primary motivation for me wanting to pass Thing
as an argument to ContainedThing::create
was to use Thing
's API to help in the construction. However, it turns out that I'll want it to be borrowed mutably, because I need recursive memoized construction here, and that makes it a cycle problem.
So, it looks like my separated check + insert logic is here to stay.
The answer is that it depends on specifically which state you need access to in the or_insert_with
closure. The problem is that or_insert_with
definitely cannot have access to the map itself because the entry api takes a mutable borrow of the map.
If all you need for ContainedThing::create
is just the size of the map, then you'll just need to calculate the map size ahead of time.
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
let map_size = self.map.len();
self.map.entry(&key).or_insert_with(|| {
// The call to entry takes a mutable reference to the map,
// so you cannot borrow map again in here
ContainedThing::create(map_size)
})
}
}
I think the spirit of the question was more about general strategies, though, so let's assume there's some other state within Thing
that is also required to create ContainedThing
.
struct Thing {
map: HashMap<i32, ContainedThing>,
some_other_stuff: AnotherType, //assume that this other state is also required in order to create ContainedThing
}
impl Thing {
pub fn get(&mut self, key: i32) -> &ContainedThing {
//this is the borrow of self
let Thing {
ref mut map,
ref mut some_other_stuff,
} = *self;
let map_size = map.len();
map.entry(key).or_insert_with(|| {
// map.entry now only borrows map instead of self
// Here you're free to borrow any other members of Thing apart from map
ContainedThing::create(map_size, some_other_stuff)
})
}
}
Whether that's really cleaner than your other solution of manually checking if self.map.contains_key(&key)
is up for debate. Destructuring tends to be the strategy that I go for, though, to allow borrowing specific members of self
instead of the entire struct.