Search code examples
typesrusthashmap

Why does inserting a value into a HashMap always result in the value being None?


I am trying to create a Cacher struct which will store calculated values in a HashMap. The calculation method will take one variable of type T, do calculations and return a value with the same type T. The type for this calculation callback will be Fn(T) -> T.

I figured out that value which will be a key of HashMap has to implement the Eq and Hash traits. It looked like everything should be working and I could compile my program without errors.

Then I wrote one test to check everything works as expected:

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

struct Cacher<T, U>
where
    T: Fn(U) -> U,
{
    calculation: T,
    values: HashMap<U, U>,
}

impl<T, U> Cacher<T, U>
where
    T: Fn(U) -> U,
    U: Eq + Hash + Clone,
{
    fn new(calculation: T) -> Cacher<T, U> {
        return Cacher {
            calculation,
            values: HashMap::new(),
        };
    }

    fn value(&mut self, arg: U) -> U {
        let result = self.values.get(&arg);
        return match result {
            Some(v) => v.clone(),
            None => self
                .values
                .insert(arg.clone(), (self.calculation)(arg.clone()))
                .unwrap_or_else(|| {
                    panic!("Unexpected error occurred");
                })
                .clone(),
        };
    }
}

#[test]
fn call_with_different_values() {
    let mut c = Cacher::new(|a: i32| a);
    let v1 = c.value(1);
    let v2 = c.value(2);
    assert_eq!(v2, 2);
}
thread 'call_with_different_values' panicked at 'Unexpected error occurred', src/lib.rs:31:21
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

When I insert a new value into my HashMap, it always finishes with Option::None, my unwrap_or_else callback is called, and then I throw an error. What am I doing wrong?


Solution

  • Your error comes from the fact that insert returns an Option with the previous value at the key, not the new one. Instead, use Entry:

    fn value(&mut self, arg: U) -> U {
        // ugly workaround to borrow checker complaining when writing these inline
        let value_ref = &mut self.values;
        let calculation_ref = &self.calculation;
        let result = value_ref.get(&arg);
        self.values.entry(arg.clone())
            .or_insert_with(|| (calculation_ref)(arg.clone()))
            .clone()
    }