How do I bound Higher Rank Trait Bound lifetimes?

Viewed 62

I have an iterator trait which uses Generic Associated Types to allow the returned values to reference data owned by the iterator:

pub trait MyIterator {
    type T<'a>
    where
        Self: 'a;

    fn next<'b>(&'b mut self) -> Option<Self::T<'b>>;
}

An example implementation looks like this:

struct FooIterator<'counter> {
    data: Vec<String>,
    counter: &'counter AtomicU32,
    index: usize
}

impl<'counter> MyIterator for FooIterator<'counter> {
    type T<'d> = &'d str
    where
        Self: 'd;
    
    fn next<'e>(&'e mut self) -> Option<Self::T<'e>> {
        self.counter.fetch_add(1, Ordering::SeqCst);
        self.index += 1;
        Some(&self.data[self.index])
    }
}

Now, I would like to have a factory that returns impl MyIterator objects without making the implementing types public. However, I can't figure out how to write the lifetimes.

My first attempt was:

struct Factory {
    data: Vec<String>,
    counter: AtomicU32,
}

impl Factory {
    fn new() -> Self {
        Factory {
            data: vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()],
            counter: AtomicU32::new(0),
        }
    }
    
    fn create<'f>(&'f self) -> impl MyIterator<T<'_> = &'_ str> {
        FooIterator {
            data: self.data.clone(),
            counter: &self.counter,
            index: 0
        }
    }
}

However, that results in a double borrowing error when trying to use the returned iterator, as can be seen here: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=6490de8f21539537764d8f628454c5c1

Next I tried using a Higher Rank Trait Bound, which seems like it should work, but I can't get the following to compile, because it seems that 'g is not in scope in the where clause (https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=beb46184de6f96bf83b64e838da4a335):

fn create<'f>(&'f self) -> impl for<'g> MyIterator<T<'g> = &'g str> + 'f where 'f: 'g {
    FooIterator {
        data: self.data.clone(),
        counter: &self.counter,
        index: 0
    }
}

Without the return impl MyIterator the code works fine, but I don't want to expose the FooIterator type. See this playground: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2c595c136077791a692ea6e0a41c057c

1 Answers

Your issue lies in the MyIterator definition, because there the next function takes a mutable borrow to self and returns something which lives as long as that mutable borrow, which is not what you would expect from an iterator.

This is subtle, because it does not make the code unsound, it just enforces excessive constraints, which in turn makes it impossible to use that trait without getting errors. A simple refactoring of lifetimes in the MyIterator trait made it work. See the playground.

Related