I'd like to use a HashMap
to cache an expensive computation that's dependent on other entries in the map. The entry pattern only provides a mutable reference to the matched value, but not to the rest of the HashMap
. I'd really appreciate feedback on a better way to solve this (incorrect) toy example:
use std::collections::HashMap;
use std::collections::hash_map::Entry::{Occupied, Vacant};
fn compute(cache: &mut HashMap<u32, u32>, input: u32) -> u32 {
match cache.entry(input) {
Vacant(entry) => if input > 2 {
// Trivial placeholder for an expensive computation.
*entry.insert(compute(&mut cache, input - 1) +
compute(&mut cache, input - 2))
} else {
0
},
Occupied(entry) => *entry.get(),
}
}
fn main() {
let mut cache = HashMap::<u32, u32>::new();
let foo = compute(&mut cache, 12);
println!("{}", foo);
}
The issue with the above snippet is that cache.entry
borrows cache
immutably, but I would like to update cache
as well.
First things first: your example can be simplified with the
.or_insert_with()
method that takes a closure which returns the value to insert at that key.It is not possible with the entry pattern to solve your problem, because you mutably borrow the cache first in your entry, and afterwards in your match (or closure). You can try it, if you use a
RefCell
(which just moves the borrowing from compiletime to runtime) it will throw a panic.To actually solve your problem, you have to split getting and inserting the value, like this:
(if you are on nightly and using
![feature(nll)]
you can omit thereturn
and useelse
on theif let
branch to make it a little bit cleaner.hellow has shown how to get working code, but I want to dive a bit more into why your code does not compile.
The code you have proposed cannot be statically verified to be memory safe. It's entirely possible that your recursive calls attempt to access the same index. Check out this simplified code for one possibility:
This now has two mutable references pointing to the same value, violating the rules of references.
Additionally, what if the inner call used
entry
and it didn't exist?Now, when you insert the value into the map via
entry2
, the map may reallocate the underlying memory, invalidating the reference held byentry1
, violating the other rule of references.Rust has prevented you from introducing two possible types of memory unsafety into your program; just like it was designed to do.