Help improving ergonomics

Hi Everyone,

I have an outward-facing interface that I don't feel is very nice, and I thought some of you who have a bit more experience can help me improve it.

use std::cell::{RefCell, Ref};
use std::slice::Iter;

pub struct TrashBag<T> {
    trash : Option<T>
}

impl <T>TrashBag<T> {
    pub fn new() -> TrashBag<T> {
        TrashBag{trash : None}
    }
    pub fn done(&mut self, input : T) -> &T {
        self.trash = Some(input);
        self.trash.as_ref().unwrap()
    }
}

pub struct Container {
    data : RefCell<RefCell<Vec<u32>>>
}

impl Container {

    // What I wish I could do.
    // 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()
    // }

    // What I have to do. :-(
    pub fn borrow_iterator<'c, 'b : 'c, 'a : 'b>(&'a self, trash_bag_1 : &'b mut TrashBag<Ref<'a, RefCell<Vec<u32>>>>, trash_bag_2 : &'c mut TrashBag<Ref<'b, Vec<u32>>>) -> std::slice::Iter<'c, u32> {
        let inner_cell_ref = trash_bag_1.done(self.data.borrow());
        let vec_ref = trash_bag_2.done(inner_cell_ref.borrow());
        vec_ref.iter()
    }
}

fn main() {

    let container = Container{data : RefCell::new(RefCell::new(vec![1u32; 2]))};

    let mut trash_bag_1 = TrashBag::new();
    let mut trash_bag_2 = TrashBag::new();
    let iter = container.borrow_iterator(&mut trash_bag_1, &mut trash_bag_2);

    for value in iter {
        println!("{}", value);
    }
}

I apologize if my last attempt to ask this question was meandering and ranty. I really appreciate any ideas about the best way to approach this.

Thanks everyone.

The main challenge here is the use of a double RefCell. With a single RefCell, it would be possible to implement a custom iterator type, but even that is not possible with a double RefCell.

How would it work with a single RefCell? It still feels like there would be some self-referencing inside the object (assuming I don't want to recreate the actual iterator, wrapped in the custom iterator object, on every call of next)

Thank you for all your help, by the way!

Hmm, with a bit more thought, it's not possible with a single either, but with a single you can make an MyIntoIter and implement IntoIterator on &MyIntoIter, and have that return an iterator. With a double RefCell, you would need yet another step of conversion to make it possible.

On a different tack, what do you think about generalizing the "TrashBag" into a single object that could be used to collect temporaries that needed to exist to support extant borrows, but the code was otherwise finished with them.

I'm remembering ObjectiveC had an object called an AutoReleasePool, that served a similar purpose. At the end of a scope, all temporaries that were still being referenced were pushed into the autorelease pool, to be dropped later.

The Rust version in my mind would be a bit more manual, as there might be some runtime overhead, such as needing such objects to be heap-allocated, in order to outlast the stack frame. (or allocated in the stack frame where the trash_pool was created)

I think the internals would need to be unsafe (to get around the internal self referencing) but I think it could be implemented with a safe interface.

Are you aware of something like this that's already been done?

Not really.

Not had much use for crate so not experienced with it.
Can't see a way to have Item &u32

#[macro_use]
extern crate rental;

use std::cell::RefCell;

pub struct Container {
    data: RefCell<RefCell<Vec<u32>>>,
}

rental! {
    pub(crate) mod rent {
        use super::Container;
        use std::cell::{RefCell, Ref};
        use std::slice::Iter;

        #[rental]
        pub(crate) struct RentIter<'l> {
            head: &'l Container,
            r1: Box<Ref<'head, RefCell<Vec<u32>>>>,
            r2: Box<Ref<'r1, Vec<u32>>>,
            suffix: Iter<'r2, u32>,
        }
    }
}

struct I<'l> {
    r: crate::rent::RentIter<'l>,
}

impl<'l> Iterator for I<'l> {
    type Item = u32;
    fn next(&mut self) -> Option<Self::Item> {
        self.r.rent_mut(|i| i.next().copied())
    }
}

impl Container {
    pub fn borrow_iterator(&self) -> impl Iterator<Item = u32> + '_ {
        I {
            r: crate::rent::RentIter::new(
                self,
                |c| Box::new(c.data.borrow()),
                |r1, _c| Box::new(r1.borrow()),
                |r2, _r1, _c| r2.iter(),
            ),
        }
    }
}

It's a placeholder for either a ref to a much bigger struct that's too expensive to copy willy-nilly, or a mutable ref. You're right that a const ref to u32 is pointless as the ref itself is 64 bits on most systems so there's no point in referencing it.

Thanks for telling me about the rental crate! I didn't realize it existed.

I seem to be walking the footsteps of @jpernst (crate author), because I spent a bunch of time yesterday trying to grok this RFC: https://github.com/rust-lang/rfcs/pull/1918 , as it seemed like the best way to implement my own kind of solution in the same vein as rental.

Thanks again!

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