Associated type referencing self without lifetime specifier

Hello.
I need to store an owned list of elements in a struct with ability to take next element (incrementing the position) and reset the position of the next element.
This is exactly what std::io::Cursor does, but it only works for &[u8].
Also it seems like I can't store in the struct a Vec<T> and iterator of it because of borrow checker rules.
So I'm trying to create a copy of std::io::Cursor, with the difference that it works for any type.

Here is one possible implementation:

pub struct CursorVec<T> {
    vec: Vec<T>,
    position: usize,
}

impl<T> CursorVec<T> {
    pub fn new(vec: Vec<T>) -> Self {
        Self { vec, position: 0 }
    }

    pub fn reset(&mut self) {
        self.position = 0;
    }

    fn next(&mut self) -> Option<&T> {
        if self.position == self.vec.len() {
            None
        } else {
            self.position += 1;
            Some(&self.vec[self.position - 1])
        }
    }
}

However, I'd like to implement Iterator trait as well, replacing the current next method.
Moving the next method to impl Iterator block:

impl<T> Iterator for CursorVec<T> {
    type Item = &T; // `&` without an explicit lifetime name cannot be used here

    fn next(&mut self) -> Option<Self::Item> {
        if self.position == self.vec.len() {
            None
        } else {
            self.position += 1;
            Some(&self.vec[self.position - 1])
        }
    }
}

But now I can't find a way to return a reference to T without adding a lifetime specifier to the whole type CursorVec.
Previously, the lifetime of &T was the lifetime of &mut self in the method next.
Can I do the same when implementing Iterator? Can I somehow specify a lifetime of &mut self in method next?

Thanks!

Iterators that wish they could return references to their contents -- like the signature of your CursorVec::next -- are known as lending iterators.[1] They would have a different signature:

trait LendingIterator {
    type Item<'a> where Self: 'a;
    fn next(&mut self) -> Option<Self::Item<'_>>;
}

This trait doesn't exist in std yet, and can't work with the for construct.


Your other option is to make a separate iterator type and implement IntoIterator for &CursorVec<T> and/or &mut CursorVec<T>. This is how the iterators your familiar with for Vec<T> are designed, for example. You could even just reuse their iterator types.

But if you wanted iteration to effect the cusror position, you'd need your own iterator type.


  1. or streaming iterators, but in more recent times "streaming iterators" is a name for async iterators ↩ī¸Ž

2 Likes

Some further observations that may be useful. First, the reason Cursor can do what it does is that it always copies the returned u8s, rather than returning a reference to any of them.

Second, if you want, you can make a borrowing cursor if advancing the cursor is done via interior mutability, so next takes &self and there are no &mut references involved. However, think twice before doing this, because this means that any holder of an &CursorVec can advance the position at any time; you lose the simplicity of &mut exclusivity.

use std::cell::Cell;

pub struct CursorVec<T> {
    vec: Vec<T>,
    position: Cell<usize>,
}

impl<T> CursorVec<T> {
    pub fn new(vec: Vec<T>) -> Self {
        Self { vec, position: Cell::new(0) }
    }

    pub fn reset(&self) {
        self.position.set(0);
    }

    pub fn next(&self) -> Option<&T> {
        let current = self.position.get();
        if current >= self.vec.len() {
            None
        } else {
            self.position.set(current + 1);
            Some(&self.vec[current])
        }
    }
}
4 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.