Hello,
This is basically a continuation of this thread: Help improving ergonomics for returning borrows from temporaries - #2 by OptimisticPeach, but the question is slightly more involved. Instead of just returning a ref from a single RefCell, I have a nested RefCell, and I want to return an iterator.
This is the essence of what I'm trying to do.
ATTEMPT #1
use std::cell::{RefCell, Ref};
pub struct Container {
data : RefCell<RefCell<Vec<u32>>>
}
impl Container {
pub fn borrow_iterator(&self) -> impl Iterator<Item=&u32> +'_ {
let inner_cell_ref = self.data.borrow();
let vec_ref = inner_cell_ref.borrow();
vec_ref.iter()
}
fn main() {
let container = Container{data : RefCell::new(RefCell::new(vec![1u32; 2]))};
let iter = container.borrow_iterator();
for value in iter {
println!("{}", value);
}
}
Obviously that doesn't work because of the dreaded "Can't borrow from temporary" error.
Following from what @OptimisticPeach said, I figured I could return an object that implemented the Iterator trait, and contained the intermediate Ref<> objects I needed, nice and hidden away from the caller.
Unfortunately I don't think the ownership rules permit an object to be moved even if it is provable that the object doesn't move in memory.
Is there a way to allocate the object on the heap, and then pass back (i.e. move ownership) of the pointer (Box<>) in such a way that doesn't piss off the borrow checker if the insides of the Box<> are actively borrowed? I couldn't make it work, but if there is, then I think it would solve the problem.
Anyway, this abomination is my second attempt at creating the clean interface I wanted to expose.
ATTEMPT #2
impl Container {
pub fn borrow_iterator(&self) -> impl Iterator<Item=&u32> +'_ {
//It's a real dance to get this structure initialized with all the self-referencing
let mut iter = MyIterator{insides : Box::new(MyIteratorInsides{inner_cell_ref : self.data.borrow(), vec_ref : None, iter : None})};
iter.insides.vec_ref = Some(iter.insides.inner_cell_ref.borrow());
iter.insides.iter = Some(iter.insides.vec_ref.as_ref().unwrap().iter());
iter
}
}
pub struct MyIteratorInsides<'a> {
inner_cell_ref : Ref<'a, RefCell<Vec<u32>>>,
vec_ref : Option<Ref<'a, Vec<u32>>>,
iter : Option<Iter<'a, u32>>
}
pub struct MyIterator<'a> {
insides : Box<MyIteratorInsides<'a>>
}
impl <'a>std::iter::Iterator for MyIterator<'a> {
type Item = &'a u32;
fn next(&mut self) -> Option<&u32> {
self.insides.iter.as_ref().unwrap().next()
}
}
//main() same as above
Not only is that horrendous internally, but it doesn't even work. At least is doesn't leak too much crap onto the caller, which is more than I can say for the next two attempts.
Thinking back to a suggestion from @alice in this thread: Borrowing from a nested RefCell - #11 by alice, I figured I could split the interface into two calls. It's not as clean as a single call to access the iterator, but it just might work. The idea is to create a custom "Ref" object, which can hold onto whatever temporaries are needed.
ATTEMPT #3
impl Container {
pub fn borrow_ref_object(&self) -> RefObject {
RefObject{inner_cell_ref : self.data.borrow(), vec_ref_opt : None}
}
}
pub struct RefObject<'b, 'a :'b> {
inner_cell_ref : Ref<'a, RefCell<Vec<u32>>>,
vec_ref_opt : Option<Ref<'b, Vec<u32>>>
}
impl <'b, 'a :'b>RefObject<'b, 'a> {
pub fn borrow_iterator(&'a mut self) -> impl Iterator<Item=&u32> +'b {
self.vec_ref_opt = Some(self.inner_cell_ref.borrow());
self.vec_ref_opt.as_ref().unwrap().iter()
}
}
fn main() {
let container = Container{data : RefCell::new(RefCell::new(vec![1u32; 2]))};
let mut ref_obj = container.borrow_ref_object();
let iter = ref_obj.borrow_iterator();
for value in iter {
println!("{}", value);
}
}
This feels all kinds of nasty. The fact that I am using the custom RefObject to stash temporaries feels like sleight of hand (the dishonest kind), and the mutable reference gives it away.
And to add insult to injury, I didn't see a way to align the lifetimes such that the RefObject could be dropped without causing a violation at the site where the borrow_iterator
function is called and the RefObject is subsequently dropped.
So that brings me to the version I know will work.
ATTEMPT #4
impl Container {
pub fn borrow_intermediate(&self) -> Intermediate {
Intermediate{inner_cell_ref : self.data.borrow()}
}
}
pub struct Intermediate<'a> {
inner_cell_ref : Ref<'a, RefCell<Vec<u32>>>
}
impl <'a>Intermediate<'a> {
pub fn borrow_intermediate2(&self) -> Intermediate2 {
Intermediate2{vec_ref : self.inner_cell_ref.borrow()}
}
}
pub struct Intermediate2<'a> {
vec_ref : Ref<'a, Vec<u32>>
}
impl <'a>Intermediate2<'a> {
pub fn borrow_iterator(&self) -> impl Iterator<Item=&u32> +'_ {
self.vec_ref.iter()
}
}
fn main() {
let container = Container{data : RefCell::new(RefCell::new(vec![1u32; 2]))};
let int1 = container.borrow_intermediate();
let int2 = int1.borrow_intermediate2();
let iter = int2.borrow_iterator();
for value in iter {
println!("{}", value);
}
}
I want to cry. I have to paste this block of 3 lines everywhere I want to get an iterator for this data?
Is there something I'm just not understanding? How would you all design such an interface? Sorry if this sounded a bit ranty. I just can't seem to find a pattern to effectively cope with this situation.
Thank you very much for reading through all that.