1

I seem to be having issues, with Rust, and would certainly appreciate some help.

I've a custom struct that implements the Iterator trait, and a separate struct (also an Iterator) that I'd like to be able to wrap anything that presents itself as an iterable. Some of this seems achievable via generics, however, when getting specific within the wrapper struct things seem to go sideways.

My main goal is to accept any iterable that returns Option<char>, and preform some parsing/collection of characters.

Here's a toy example that demonstrates the core features I'm trying to implement...

src/main.rs

#!/usr/bin/env rust


struct IteratorHolder<I, T>
where
    I: Iterator<Item = T>
{
    iter: I,
}

impl<I, T> Iterator for IteratorHolder<I, T>
where
    I: Iterator<Item = T>
{
    type Item = T;

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

impl<I, T> IteratorHolder<I, T>
where
    I: Iterator<Item = T>
{
    #[allow(dead_code)]
    fn new(iter: I) -> Self {
        Self { iter }
    }
}

impl<I, T> From<I> for IteratorHolder<I, T>
where
    I: Iterator<Item = T>,
{
    fn from(iter: I) -> Self {
        Self { iter }
    }
}


// ============================================================================


struct CustomIterator {
    data: Option<(usize, String)>
}

impl CustomIterator {
    #[allow(dead_code)]
    fn new<S>(string: S) -> Self
    where
        S: Into<String>
    {
        let string: String = string.into();
        let data = Some((0, string));
        Self { data }
    }
}

impl Iterator for CustomIterator {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some((index, string)) = self.data.take() {
            let mut iter = string.get(index..).unwrap().char_indices();
            if let Some((_, c)) = iter.next() {
                self.data = iter.next().map(|(i, _)| (index + i, string));
                return Some(c);
            }
        }
        None
    }
}


// ============================================================================


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn map() {
        let v = vec![1, 2, 3];
        let m = v.into_iter().map(|n| n * 2);
        let mut i = IteratorHolder::new(m);
        assert_eq!(i.next(), Some(2));
    }

    #[test]
    fn chars() {
        let s = String::from("abc");
        let c = s.chars();
        let mut i = IteratorHolder::new(c);
        assert_eq!(i.next(), Some('a'));
    }

    #[test]
    fn custom_iterator() {
        let c = CustomIterator::new("zxc");
        let mut i = IteratorHolder::new(c);
        assert_eq!(i.next(), Some('z'));
    }

    #[test]
    fn from_iter() {
        let c = CustomIterator::new("zxc");
        let mut i = IteratorHolder::from(c);
        assert_eq!(i.next(), Some('z'));
    }
}

Above all seems to function without errors


What I'm finding strange is when getting specific with From implementations there be various type errors appearing.

For example where I to implement FromStr on IteratorHolder a E0053 error is generated...

method from_str has an incompatible type for trait expected fn pointer fn(&str) -> std::result::Result<IteratorHolder<I, T>, _> found fn pointer fn(&str) -> std::result::Result<IteratorHolder<CustomIterator, char>, _> [E0053]

src/main.rs (snip)

use std::str::FromStr;
use std::num::ParseIntError;


impl<I, T> FromStr for IteratorHolder<I, T>
where
    I: Iterator<Item = T>,
{
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<IteratorHolder<CustomIterator, char>, Self::Err> {
        let iter = CustomIterator::new(s);
        let hold = IteratorHolder { iter };
        Ok(hold)
    }
}

... And being more generic generates E0308 errors...

mismatched types expected type parameter I found struct CustomIterator [E0308]

impl<I, T> FromStr for IteratorHolder<I, T>
where
    I: Iterator<Item = T>,
{
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let iter = CustomIterator::new(s);
        let hold = Self { iter };
        Ok(hold)
    }
}

Essentially I'm face-planting on how to make IteratorHolder more self sufficient, with regards to From implementations. My main goal is to have a parser, similar to IteratorHolder, that can read characters from iterators regardless of where those characters are sourced from; eg. file, stream, string. etc.

1
  • 1
    When you say impl<I, T> FromStr for IteratorHolder<I, T> where I: Iterator<Item = T> you're saying "for every IteratorHolder I will implement FromStr." Yet, your implementation returns only an IteratorHolder for CustomIterator, which is clearly not what your type declaration said. Commented May 25, 2021 at 0:40

1 Answer 1

1

As @Aplet123 mentioned, you are claiming to implement FromStr for an IteratorHolder containing any iterator, however you really just want to implement it for IteratorHolder<CustomIterator>. Here's a working version, cleaned up a bit and more idiomatic (particularly: I removed T since it is redundant, it already exists as I::Item):

Playground

use std::num::ParseIntError;
use std::str::FromStr;

struct IteratorHolder<I> {
    iter: I,
}

impl<I: Iterator> Iterator for IteratorHolder<I> {
    type Item = I::Item;

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

impl<I: Iterator> IteratorHolder<I> {
    #[allow(dead_code)]
    fn new(iter: I) -> Self {
        Self { iter }
    }
}

impl<I: Iterator> From<I> for IteratorHolder<I> {
    fn from(iter: I) -> Self {
        Self::new(iter)
    }
}

// ============================================================================

struct CustomIterator {
    data: Option<(usize, String)>,
}

impl CustomIterator {
    #[allow(dead_code)]
    fn new<S: Into<String>>(string: S) -> Self {
        Self {
            data: Some((0, string.into())),
        }
    }
}

impl Iterator for CustomIterator {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if let Some((index, string)) = self.data.take() {
            let mut iter = string.get(index..).unwrap().char_indices();
            if let Some((_, c)) = iter.next() {
                self.data = iter.next().map(|(i, _)| (index + i, string));
                return Some(c);
            }
        }
        None
    }
}

impl FromStr for IteratorHolder<CustomIterator> {
    type Err = ParseIntError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(IteratorHolder {
            iter: CustomIterator::new(s),
        })
    }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Thank you! That totally worked!... Here I've been making a proverbial forehead shaped stain against a wall of compiler errors for days. I'll certainly have to spend some more time with the corrected code to fully grasp where I went wrong.
Glad I could help! To be clear, the T parameter wasn't the cause of any errors, it was just redundant. The issue was that writing impl<I: Iterator> FromStr for IteratorHolder<I> is like claiming that you would can convert a string into a holder containing whatever kind of iterator that the consumer of the impl desires. Instead of saying "I can turn a string into IteratorHolder<CustomIterator>", you were saying "I can turn a string into IteratorHolder<AnyIteratorYouWant>", which isn't even possible. How can you turn a string into any possible iterator, e.g. a network download iterator?
The reason why that is correct for things like From<I> is that you would be correct to say "I can turn any type I into IteratorHolder<I>". On the other hand, you can't say "I can convert a string into IteratorHolder<I> for any type I".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.