1

I'm creating a lazily loading struct:

pub struct LazyData<T, U:FnOnce() -> T> {
    data:      RefCell<Option<T>>,
    generator: RefCell<Option<U>>,
}

impl<T,U:FnOnce() -> T> LazyData<T,U> {
    pub fn new(generator:U) -> LazyData<T,U> {
        LazyData {
            data:      RefCell::new(None),
            generator: RefCell::new(Some(generator)),
        }
    }
    ...
}

I've tried to implement a get method that will return the lazily loaded data:

pub fn init(&self) {
    if self.data.borrow().is_none() {
        let generator = self.generator.replace(None).unwrap();
        let _none     = self.data.replace(Some(generator()));
    };
}

pub fn get(&self) -> &T {
    self.init();
    self.data.borrow().as_ref().unwrap()
}

But the compiler complains that I'm returning a value referencing data owned by the current function. This error makes sense to me, but I'm not sure how to work around it.

Here's a link to a full example.

trexinf14s
  • 261
  • 2
  • 13

1 Answers1

0

Found a workaround here! I can use Ref::map to get to the inner data:

pub fn get(&self) -> Ref<T> {
    self.init();
    Ref::map(self.data.borrow(), |borrow| {
        borrow.as_ref().unwrap()
    })
}

This method now returns Ref instead of &T, but I think that should be fine.

trexinf14s
  • 261
  • 2
  • 13
  • I will leave this question open for a few days in case anyone has a better solution/something to add! – trexinf14s Apr 07 '20 at 17:03
  • 1
    You can't return a `&T` because the `borrow()` on the `RefCell` creates a temporary. It let's go of the `RefCell`-borrow once the `Ref` is destroyed. Think about it: By returning a `Ref`, you are effectively write-locking the `RefCell` until the `Ref` is dropped by whoever the caller of `get()` is. This seems like poor isolation. Why is the `RefCell` there in the first place? – user2722968 Apr 07 '20 at 17:08
  • Thanks for your response! I've wrapped `data` in a `RefCell` so that the `get` method doesn't require `&mut self`. Without the `RefCell`, `init` would require `&mut self` in order to change the `data` field from `None` to `Some(...)`, and so every basic interaction with `LazyData` would require mutability. – trexinf14s Apr 07 '20 at 17:15
  • I'd suggest to rethink this. The fact that you do not want mutable access to `init()` causes `get()` to leak the fact-checker that it is *not* currently mutably borrowed via `Ref` to the caller of `get()`. This is robbing Peter to pay Paul. – user2722968 Apr 07 '20 at 18:31