Carl
Carl

Reputation: 383

In Rust, is it OK for an (non-moving) iter to return owned values rather than references?

For example, this works:

pub struct SquareVecIter<'a> {
    current: f64,
    iter: core::slice::Iter<'a, f64>,
}

pub fn square_iter<'a>(vec: &'a Vec<f64>) -> SquareVecIter<'a> {
    SquareVecIter {
        current: 0.0,
        iter: vec.iter(),
    }
}

impl<'a> Iterator for SquareVecIter<'a> {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(next) = self.iter.next() {
            self.current = next * next;
            Some(self.current)
        } else {
            None
        }
    }
}

// switch to test module
#[cfg(test)]
mod tests_2 {
    use super::*;

    #[test]
    fn test_square_vec() {
        let vec = vec![1.0, 2.0];
        let mut iter = square_iter(&vec);
        assert_eq!(iter.next(), Some(1.0));
        assert_eq!(iter.next(), Some(4.0));
        assert_eq!(iter.next(), None);
    }
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=531edc40dcca4a79d11af3cbd29943b7

But if I have to return references to self.current then I can't get the lifetimes to work.

Upvotes: 0

Views: 64

Answers (2)

Finomnis
Finomnis

Reputation: 22601

What @kmdreko says, of course.

I just wanted to add a couple of nitpicks:

  • The pattern of the if let Some(...) = ... {Some} else {None} is so common that it made its way into the standard library as the .map function.
  • Taking a &Vec<f64> is an antipattern. Use &[f64] instead. It is more general without any drawbacks.
  • All of your lifetime annotations (except of the one in the struct definition) can be derived automatically, so you can simply omit them.
pub struct SquareVecIter<'a> {
    iter: core::slice::Iter<'a, f64>,
}

pub fn square_iter(vec: &[f64]) -> SquareVecIter {
    SquareVecIter { iter: vec.iter() }
}

impl Iterator for SquareVecIter<'_> {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter.next().map(|next| next * next)
    }
}

Upvotes: 1

kmdreko
kmdreko

Reputation: 60152

Yes.

An Iterator cannot yield elements that reference itself simply due to how the trait is designed (search "lending iterator" for more info). So even if you wanted to return Some(&self.current) and deal with the implications therein, you could not.

Returning an f64 (non-reference) is perfectly acceptable and would be expected because this is a kind of generative iterator. And you wouldn't need to store current at all:

pub struct SquareVecIter<'a> {
    iter: core::slice::Iter<'a, f64>,
}

pub fn square_iter<'a>(vec: &'a Vec<f64>) -> SquareVecIter<'a> {
    SquareVecIter {
        iter: vec.iter(),
    }
}

impl<'a> Iterator for SquareVecIter<'a> {
    type Item = f64;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some(next) = self.iter.next() {
            Some(next * next)
        } else {
            None
        }
    }
}

For an example of this in the standard library, look at the Chars iterator for getting characters of a string. It keeps a reference to the original str, but it yields owned chars and not references.

Upvotes: 3

Related Questions