5

I am trying to improve the Cacher type described in The Rust Programming Language. One of the improvements suggested states that Cacher should be usable with many types. To do this, I have written the following code:

struct Cacher<T, U>
where
    T: Fn(&U) -> U,
{
    calculation: T,
    value: Option<U>,
}

impl<T, U> Cacher<T, U>
where
    T: Fn(&U) -> U,
{
    fn new(calculation: T) -> Cacher<T, U> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: &U) -> &U {
        match self.value {
            Some(v) => &v,
            None => {
                let v = (self.calculation)(arg);
                self.value = Some(v);
                &v
            }
        }
    }
}

The compiler complains:

error[E0507]: cannot move out of `self.value.0` which is behind a mutable reference
  --> src/lib.rs:21:15
   |
21 |         match self.value {
   |               ^^^^^^^^^^ help: consider borrowing here: `&self.value`
22 |             Some(v) => &v,
   |                  -
   |                  |
   |                  data moved here
   |                  move occurs because `v` has type `U`, which does not implement the `Copy` trait

error[E0515]: cannot return reference to local variable `v`
  --> src/lib.rs:22:24
   |
22 |             Some(v) => &v,
   |                        ^^ returns a reference to data owned by the current function

error[E0515]: cannot return reference to local variable `v`
  --> src/lib.rs:26:17
   |
26 |                 &v
   |                 ^^ returns a reference to data owned by the current function

error[E0382]: borrow of moved value: `v`
  --> src/lib.rs:26:17
   |
24 |                 let v = (self.calculation)(arg);
   |                     - move occurs because `v` has type `U`, which does not implement the `Copy` trait
25 |                 self.value = Some(v);
   |                                   - value moved here
26 |                 &v
   |                 ^^ value borrowed here after move

I imagine this is because v is local to the function. However, given that the actual data exists within the struct which exists beyond the value method, is it not possible to return reference to this data in any way? If not this way, then what would I have to do to get the functionality of a Cacher that owns the calculated data and returns the references whenever required?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
abhink
  • 8,740
  • 1
  • 36
  • 48
  • 2
    it looks like [`Option::get_or_insert_with`](https://doc.rust-lang.org/std/option/enum.Option.html#method.get_or_insert_with) would help here – kmdreko Sep 20 '20 at 15:18

2 Answers2

2

You almost got it. You just need to return the reference to the object inside the Option:

struct Cacher<T, U>
where
    T: Fn(&U) -> U
{
    calculation: T,
    value: Option<U>,
}

impl<T, U> Cacher<T, U>
where
    T: Fn(&U) -> U
{
    fn new(calculation: T) -> Cacher<T, U> {
        Cacher {
            calculation,
            value: None,
        }
    }

    fn value(&mut self, arg: &U) -> &U {
        if self.value.is_none() {
            let v = (self.calculation)(arg);
            self.value = Some(v);
        }
        self.value.as_ref().unwrap()
    }
}

Playground

Netwave
  • 40,134
  • 6
  • 50
  • 93
1

A simpler and slightly more efficient alternative is to use get_or_insert_with:

fn value(&mut self, arg: &U) -> &U {
    let calculation = &self.calculation;
    self.value.get_or_insert_with(|| calculation(arg))
}
L. F.
  • 19,445
  • 8
  • 48
  • 82