Borrowing Ref into Iterator

Currently we have this:

    fn for_names<F: FnMut(&'pat str)>(&self, f: F) {
        let strings = &self.interp.pat.strings;
        self.interp.frames.borrow().iter().filter_map(|frame| {
            match frame.op() {
                | PatternElement::Value { name_and_value, .. }
                | PatternElement::Tag { name_and_value, .. }
                if frame.matches && name_and_value.is_here()
                => {
                    Some(&*strings[name_and_value.here().unwrap()])
                },
                _ => None,
            }
        }).for_each(f)
    }

We'd like to have this:

    fn for_names(&self) -> impl Iterator<Item=&'pat str> {
        let strings = &self.interp.pat.strings;
        self.interp.frames.borrow().iter().filter_map(|frame| {
            match frame.op() {
                | PatternElement::Value { name_and_value, .. }
                | PatternElement::Tag { name_and_value, .. }
                if frame.matches && name_and_value.is_here()
                => {
                    Some(&*strings[name_and_value.here().unwrap()])
                },
                _ => None,
            }
        })
    }

But because the iter() comes from a Ref some lifetime issues happen.

Unfortunately we really have no idea how to fix this. self_cell doesn't work here because it doesn't support lifetimes (specifically the 'pat) so that's unfortunate.

Have you considered the lifetime elision?

fn for_names(&self) -> impl Iterator<Item = &str> {
fn for_names2(&self, f: impl FnMut(&str)) {

Ah right uh. self.interp.frames is a &'state RefCell<Vec<Frame<...>>>. We guess that wasn't as clear as we were hoping.

so no, that doesn't work here.

see, std has Ref-to-Ref projection (&T -> &U, aka Ref::map) but not Ref-to-"BorrowsRef" (&T -> U<'_>) projection. and those would only be possible with GATs anyway.

We could do something along the lines of

struct RefIteratorInner<'this, 'a, T> {
  borrowed: Ref<'a, Vec<T>>,
  owned: RefCell<Option<Iter<'this, T>>>, // need the option because reasons
}
struct RefIterator<'a, T> {
  inner: Pin<Box<Holder<'a, RefIteratorKey<'a, T>>>>,
}
impl<'a, T> Iterator for RefIterator<'a, T> {
  fn next(&mut self) {
    self.inner.as_ref().operate_in(|inner|{ // can't actually use a closure here but pretend we could
      inner.owned.borrow_mut().unwrap().next()
    })
  }
}

if we were to use selfref. We believe this to be less than ideal tho.

Have you considered using a more powerful self-referential library, like ouroboros?

ouroboros is actually less powerful because it can't do this at all.

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

pub type Frame = std::ops::Range<usize>;

pub struct MyStruct {
    some_string: String,
    frames: RefCell<Vec<Frame>>,
}

impl MyStruct {
    pub fn for_names(&self) -> MyIterator<'_> {
        MyIterator::new(
            self.frames.borrow(),
            |frames| frames.iter(),
            &self.some_string,
        )
    }
}

#[ouroboros::self_referencing]
pub struct MyIterator<'my_struct> {
    frames: Ref<'my_struct, Vec<Frame>>,
    #[borrows(frames)]
    #[covariant]
    iter: Iter<'this, Frame>,
    some_string: &'my_struct String,
}

impl<'my_struct> Iterator for MyIterator<'my_struct> {
    type Item = &'my_struct str;

    fn next(&mut self) -> Option<Self::Item> {
        let frame = self.with_iter_mut(|iter| {
            iter.find_map(|frame| {
                // let's pretend you do some real work here
                Some(frame)
            })
        })?;
        let frame = frame.start..frame.end;
        Some(&self.borrow_some_string()[frame])
    }
}

This seems to work and match your example though

1 Like

wait what since when does ouroboros work with lifetimes, we thought it didn't?

My example seems to works since ouroboros 0.8, when they introduced #[covariant] (the latest version is 0.15)

hmm, we wonder if there's a crate that works with Ref directly. like uh.

because like, Ref doesn't actually require pinning or allocation to borrow like this. but using any of the selfref crates available (selfref, ouroboros, self_cell, etc) would require pinning/allocations because they need those for soundness.

then again, since this isn't a public API, maybe we shouldn't bother. it's not like it makes much of a difference to return the iterator vs to call a closure with it for our needs.

oh, we guess we can use a level of indirection actually. have a wrapper type which contains the Ref or RefMut, and then have methods to iterate on it.