Delegate iteration to field Vec

/// Scalar Value string type.
#[derive(Clone, PartialEq, PartialOrd)]
pub struct SvStr {
    m_chars: Vec<char>,
}

impl SvStr {
    pub fn iter(&self) -> Iter<char> {
        self.m_chars.iter()
    }
}

#[cfg(test)]
mod tests {
    use super::SvStr;
    #[test]
    fn iteration() {
        let i = SvStr::from("foo").iter();
        assert_eq!(*(i.next().unwrap()), 'f');
    }
}

There are weird things happening here. iter() is yielding &char instead of simply char and for..in doesn't work:

for c in SvStr::from("foo") {
}

Also, in the #[test] method, I'm getting:

lib.rs(244, 17): original diagnostic
cannot borrow `i` as mutable, as it is not declared as mutable
cannot borrow as mutable

Any help please? I also tried:

impl IntoIterator for SvStr {
    type Item = char;
    type IntoIter = IntoIter<char>;

    fn into_iter(self) -> Self::IntoIter {
        self.m_chars.into_iter()
    }
}
1 Like

You can have item type char like this:

impl SvStr {
    pub fn iter(&self) -> impl Iterator<Item = char> + '_ {
        self.m_chars.iter().copied()
    }
}

The return type here uses the impl Trait feature.

2 Likes

Thank you! Do you know why the following fails:

let x = Vec::<String>::new().iter(); // works
let x = SvStr::from("foo").iter(); // doesn't work

The second line works if I store the SvStr in another previous variable.

It's a combination of a few things:

  • Both iterators borrow from the structures created with new
  • The structures are temporaries that drop on the same line
  • The iterators are stored in variables that drop at the end of the block, so after the structures
  • Your iterator potentially has a non-trivial destructor (a Drop implementation)
    • Where as the Vec iterator (slice::Iter<'_, _>) has a trivial one

So it would be possible for the destructor of your iterator to observe the SvStr after it has dropped. The first line works because the compiler generates the trivial constructors and it knows they don't observe their contents, it just recursively drops them.

As it turns out, the iterator you are returning does not have a non-trivial destructor. However, one of the points of return-position impl Trait (RPIT) is to give the function author the flexibility to change what underlying concrete type is returned in a non-breaking fashion, so long as it implements the trait. But a flip side of that is the compiler must act like you have a Drop implementation, because some future type you return might have one, even though your current one doesn't.

Returning a concrete type with a trivial destructor works. If you want to maintain more flexibility to change in the future without breaking downstream, you could wrap that concrete type in a newtype of your own, implement Iterator for that, and so on.

1 Like