Lifetime problem with Chapter 13 expensive_calculation

Hi,

I am working through The Rust Programming Language book and getting an error with compiling a test when I have implemented the generic version of the Cacher type:

error: lifetime may not live long enough
  --> src\main.rs:78:37
   |
78 |     let mut  c = Cacher::new(| a|   a);
   |                                --   ^ returning this value requires that `'1` must outlive `'2`
   |                                ||
   |                                |return type of closure is &'2 &i32
   |                                has type `&'1 &i32`

error: aborting due to previous error

The type deceleration and test code:

 use std::thread;
use std::time::Duration;
use std::collections::HashMap;
use std::hash::Hash;
use std::collections::hash_map::Entry;
use std::ops::Fn;
use std::cmp::Eq;

struct Cacher<T,U,V>
where
T: Fn(&U) -> V,
U: Hash + Eq + Clone,
V: Clone ,
{
    calculation: T,
    value: HashMap<U,V>,
}

impl<T,U,V> Cacher<T,U, V>
where
T: Fn(&U) -> V,
U: Hash + Eq + Clone,
V: Clone ,
{
    fn new(calculation:  T) ->   Cacher<T,U,V> {
        Cacher {
            calculation ,
            value: HashMap::new(),
        }
    }

    fn value (&mut self, arg: U) -> &V {
        
        match self.value.entry(arg) {

            Entry::Occupied(entry) => entry.into_mut(),

            Entry::Vacant(entry) => {
                let v = (self.calculation)(entry.key());
                entry.insert(v)
            }
            
        }
    }
}

I have tried various changes for lifetimes in the new and value functions but so far no luck.

Any ideas?

Thanks,
Frank

The closure you use here, |a| a, is one whose return type is the same as its input type. This means that &U==V. Keep this in mind.
Let's look at value:

impl<T, U, V> Cacher<T, U, V>
where
    T: Fn(&U) -> V,
    U: Hash + Eq + Clone,
    V: Clone,
{
    // --snip--
    fn value(&mut self, arg: U) -> &V {
        // --snip--
    }
}

fn main() {
    let mut c = Cacher::new(|a| a);
    let value = c.value(5);
}

The call to value is pretty important here. Its arg parameter is of type U. By calling c.value(5), you give the compiler enough info to know that U is of type i32 in this case. Now we can fill in the generic types.

    T: Fn(&U) -> V, // T is `Fn(&i32) -> V`, what is V?
    U: Hash + Eq + Clone, // U is i32
    V: Clone,

but we know that the closure's return type is the same as its input type, so we can fill in more info:

    T: Fn(&U) -> V, // T is `Fn(&i32) -> V`, but `&i32==V`, therefore V is &i32
    U: Hash + Eq + Clone, // U is i32
    V: Clone, // V is &i32

The type of the hashmap ends up being HashMap<i32, &i32>, which not only doesn't seem right (in most cases you want the hashmap to own its values), but in this case could also lead to dangling pointers.

78 |     let mut  c = Cacher::new(| a|   a);
   |                                --   ^ returning this value requires that `'1` must outlive `'2`
   |                                ||
   |                                |return type of closure is &'2 &i32
   |                                has type `&'1 &i32`

In this case, the value (&i32) is a reference to the key (i32). The key (which has lifetime '1) must outlive its reference (&'2 i32) in order for the reference to be valid.
If the HashMap ever reallocates its data, the keys will no longer outlive their references.


One way to solve this is to copy/clone the value in the closure. If the type is Copy, like i32 is, you can just dereference the reference:

fn main() {
    let mut c = Cacher::new(|a| *a); // Fn(&i32) -> i32
    let value = c.value(5);
}

You can also clone the value if the type isn't Copy:

fn main() {
    let mut c = Cacher::new(|a: &String| a.clone()); // Fn(&String) -> String
    let value = c.value(String::from("Hi!"));
}

You do have to provide type annotations for the closure here, as the compiler isn't able to to infer the type in this case.
Really, any closure that provides an owned value will work:

fn main() {
    let mut c = Cacher::new(|a: &String| String::from("Bob said: ") + a);
    dbg!(c.value(String::from("Hi!"))); // prints "Bob said: Hi!"
}

Thanks very much. Part of the problem in learning Rust is dealing with the error message. When I first tried the test case from the book, which I failed to put in the listing, after creating a generic Cacher the code:

fn call_with_different_values() {
    let mut  c = Cacher::new(| a|   *a);

    let v1 = c.value(1);
    let v2 = c.value(2);

    assert_eq!(*v2, 2);
}

Gave the above error message of lifetimes of '1 and '2. I knew that it couldn't be the arg values of 1 and 2 but I was still confused. I didn't pull it together that it was referring to the key value fields of the HashMap.

Thanks,
Frank

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.