Frustration making clean interfaces, part 2

Hello,

This is basically a continuation of this thread: Help improving ergonomics for returning borrows from temporaries, 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, 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.

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