Lifetime error: cached member function

Hi,

as a Rust newbie, I am struggling putting the (right) pieces together for implementing a member function, which does some computation on the first run and on subsequent ones returns the cached value.

In the below implementation I get lifetime error:

cannot infer an appropriate lifetime due to conflicting requirements

note: ...so that the types are compatible:
      expected &[MyData<'_>]
         found &[MyData<'a>]
note: but, the lifetime must be valid for the static lifetime...
note: ...so that the expression is assignable:
      expected MyData<'static>
         found MyData<'_>

For storing the cached/not-cached state, I chose Option.
For allowing interior mutability, I chose I chose RefCell.
For having one type for both owned/non-owned data, I chose Cow.

It's likely any of these choices were ill-advised. That's were you come in :wink:

This is how far I got:

use std::borrow::Cow;
use std::cell::RefCell;

#[derive(Clone)]
struct MyData<'a> {
    name: Cow<'a, str>,
    numbers: Cow<'a, [i32]>,
}

impl<'a> MyData<'a> {
    fn new() -> Self {
        let s = "Hello";
        let is = &[1, 2];
        MyData {
            name: Cow::Borrowed(s),
            numbers: Cow::Borrowed(is),
        }
    }
}

struct Container<'a> {
    data: Vec<MyData<'a>>,
    cache_first_element: RefCell<Option<MyData<'static>>>,
}

impl<'a> Container<'a> {
    fn new() -> Self {
        Container {
            data: vec![],
            cache_first_element: RefCell::new(None),
        }
    }
}

use std::iter::IntoIterator;

impl<'a> Container<'a> {
    /// I want to store a copy locally in Container, but on `first_element` calls return a ref to it 
    /// instead of moving/ copying
    fn first_element(&self) -> &MyData<'static> {
        match self.cache_first_element.borrow().as_ref() {
            Some(d) => &d,
            None => {
                self.cache_first_element.replace(
                    self.data
                        .iter() // error: cannot infer lifetime 
                        .take(1)
                        .cloned()
                        .map(|r| -> MyData<'static> {
                            MyData {
                                name: r.name.to_owned(),
                                numbers: r.numbers.to_owned(),
                            }
                        })
                        .next(),
                );
        }
    }
}

fn foo<'a>() -> Container<'a> {
    let data = MyData::new();
    let mut c = Container::new();
    c.data.push(data.clone());
    c.data.push(data.clone());
    c
}

Short answer

You're effectively trying to increase 'a lifetime to 'static. Generally this is not a sound conversion, perhaps you meant

fn first_element(&self) -> &MyData<'a>

instead.

Detailed answer

The 'static lifetime literally means "valid for the whole program runtime, from the creation to the very end". An 'a lifetime means "whatever lifetime it would be". The declaration of slice::iter method (unprettyfied):

pub fn iter(&'b self) -> Iter<'b, T>

When you call data.iter() rust must infer this 'b lifetime.

  • It looks at the self lifetime and realizes it must not outlive 'a, hence 'b cannot outlive 'a
  • It looks at return type (because you use this iter as a part of return expression) and realizes that 'b must live at least as long as 'static

It's pretty clear that "whatever lifetime" may or may not be as long as 'static so rust rejects this code as unsound.

Why do you need 'static in the first place?

And about the Cow thing - only some specific sort of generic code can actually benefit from it, in most cases it brings in more complexity than profit. I'm not sure what you case would be (explain?) but in my opinion this should work just fine

#[derive(Clone)]
struct MyData<'a> {
    name: &'a str,
    numbers: &'a [i32],
}

Thanks!
About the Cow thing:
Basically because MyData is really large and I want to avoid copying until neccessary:

And following your 'a suggestion, I now get that I am returning a temporary.
Which I kinda get, probably because of the RefCell returning the Ref object to do the runtime refcounting, but I can also not return a Ref object.


struct Container<'a> {
    data: Vec<MyData<'a>>,
    cache_first_element: RefCell<Option<MyData<'a>>>,
}

impl<'a> Container<'a> {
    fn new() -> Self {
        Container {
            data: vec![],
            cache_first_element: RefCell::new(None),
        }
    }
}

use std::iter::IntoIterator;

impl<'a> Container<'a> {
    /// I want to store a copy locally in Container, but on `first_element` calls return a ref to it
    /// instead of moving/ copying
    fn first_element(&self) -> &MyData<'a> {
        match self.cache_first_element.borrow().as_ref() {
            Some(d) => d,
            None => &self
                .cache_first_element
                .replace(
                    self.data
                        .iter()
                        .take(1)
                        .cloned()
                        .map(|r| -> MyData<'a> {
                            MyData {
                                name: r.name.to_owned(),
                                numbers: r.numbers.to_owned(),
                            }
                        })
                        .next(),
                )
                .unwrap(),
        }
    }
}

And why can't you just return a Ref, exactly?

Also, I see you do

MyData {
    name: r.name.to_owned(),
    numbers: r.numbers.to_owned(),
}

essentially cloning the data, which is what you're trying to avoid. Why Cow?

Btw, you can use slice::get instead of .iter().take(1).next()

2 Likes

Furthermore, None.replace(...).unwrap() panics unconditionally, because RefCell::replace returns old value (the one is to be replaced), which is None

1 Like

If you have a RefCell, you cannot return a borrow to stuff inside of it. You have to return a Ref.

struct Struct(RefCell<Field>);

impl Struct {
    fn field(&self) -> &Field;         // <-- impossible
    fn field(&self) -> Ref<'_, Field>; // <-- possible
}

This is because RefCell wants to know when the borrow of its contents goes out of scope so that the refcount can be decremented.

In this case, I'm not sure that I see any reason why you cannot return Ref. That said, when using caches, I typically just put the value in an Rc so that it can be cheaply cloned.

struct Struct(RefCell<Option<Rc<Field>>>);

impl Struct {
    fn field(&self) -> Rc<Field> { ... }
}
2 Likes

Thanks you guys! The rust community is awesome!

I can't return Ref because it would be a ref to the option and not the value returned in the option.

This is how far I got now:

#[derive(Debug)]
struct Container<'a> {
    data: Vec<MyData<'a>>,
    cache_first_element: RefCell<Option<MyData<'a>>>,
}

impl<'a> Container<'a> {
    fn new() -> Self {
        Container {
            data: vec![],
            cache_first_element: RefCell::new(None),
        }
    }
}

impl<'a> Container<'a> {
    fn first_element(&self) -> std::cell::Ref<Option<MyData<'a>>> {
        self.cache_first_element
            .borrow_mut()
            .get_or_insert_with(|| self.data.get(0).unwrap().clone().to_owned());

        self.cache_first_element.borrow()
    }

it kinda does all the things I want: copies once, returns references every time after to avoid copies.
It has an ergonomic issues as in it returns the ref to the option and not to the MyData.
Reason being that unwrap on the Option returns a temporary.

Ideally, I could also return a MyData, which is non-owning and points to the new local variable. But I didn't get that to work because it of referencing a temporary.

I think Ref::map should help here.

1 Like

You're right!
Persistence pays off. I wasn't aware of Option::as_ref:

impl<'a> Container<'a> {
    fn first_element(&self) -> std::cell::Ref<MyData<'a>> {
        self.cache_first_element
            .borrow_mut()
            .get_or_insert_with(|| self.data.get(0).unwrap().clone().to_owned());

        std::cell::Ref::map(self.cache_first_element.borrow(), |o| o.as_ref().unwrap())
    }
}

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.