Problem with RefCell<T> and Ref<T> lifetimes

#1

Hi!

I am trying to return an iterator over references into a Vec. The problem is, that the structure containing the Vec is inside a Rc<RefCell<T>>.

struct TheInner {
    items: Vec<String>,
}

impl TheInner {
    fn enumerate(&self) -> impl Iterator<Item = &String> {
        self.items.iter()
    }
}

struct Wrapper(Rc<RefCell<TheInner>>);

impl Wrapper {
    fn enumerate(&self) -> impl Iterator<Item = &String> {
        self.0.borrow().enumerate()
    }
}

I understand, why this can not work. The documentation of Ref<T> says, that the data can be accessed as long as it is in scope. In my example, it will drop in the enumerate() function, so I an not able to call the inner enumerate() method.

Is there some kind of workaround for this? I already tried everything I could think of including creating a new iterator, which owns the Rc, or the Ref, but nothing worked so far.

The main problem is, that I can not return any references from the Ref in a function.

#2

Conceptually, you need to use Ref::map there, to make the iterator hold the lock on the data. However, map works only with plain references. So, if you are fine with exposing &[String], you can do this:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=bcbb3840f9ba1cc63648d9ae26ebb387

Alternatively, you can make an internal-iterator based API:

fn for_each(&self, f: impl FnMut(&String))
#3

One can also hide the Ref in the return type via impl trait:

fn items(&self) -> impl Deref<Target = [String]> + '_ {
        Ref::map(self.0.borrow(), |it| it.items())
}

In general, I find it easier to handle RefCell cases by exposing an internal iteration API, like @matklad’s for_each() suggestion.

3 Likes
#4

Wow, impl Deref is super neat, thanks!

#5

Very nice solution!

#6

Wow, Ref::map_split() is also something I did not know about Ref. Very useful for my case.

#7

Good to see that people are finding that method useful. I was initially somewhat opposed to it out of concern that people would soon encounter situations where they need to individually borrow more than 2 fields (since this cannot be achieved with the 2-reference function alone), but if a decent number of people are satisfied with this limitation, that’s great.

#8

I am using it by splitting off one field at a time.

#9

Hm? That’s only possible if your struct is structured like a cons-list. (because you can’t get a & to "`this struct minus all the other fields)

…wait a second, I see. You can return the entire input itself as one of the tuple elements:

let (obj, field) = obj.map_split(|obj| (obj, &obj.field));
let (obj, another) = obj.map_split(|obj| (obj, &obj.another));

Okay, so that works. I guess the problem then is only for mutable borrows (RefMut::map_split).