Home > Enterprise >  Can't assign `std::str::Chars` to a variable of type `I: Iterator`
Can't assign `std::str::Chars` to a variable of type `I: Iterator`

Time:12-05

I'm trying to create a custom iterator over a string's characters, with the difference that it "splits" the string into two iterators. Which one is then used in its own next() depends on custom logic.

struct WrappingIter<I> {
    iter_1: I,
    iter_2: I,
    // ...
}

impl<I> WrappingIter<I>
where
    I: Iterator,
{
    pub fn new(string: &str, start_idx: usize) -> Self {
        Self {
            iter_1: string[start_idx..].chars(),
            iter_2: string[..start_idx].chars(),
            // ...
        }
    }
}

That gives me this error (for both assignments):

 1  error[E0308]: mismatched types
   --> src/lib.rs:38:25
    |
 29 |     impl<I> WrappingIter<I>
    |          - this type parameter
 ...
 38 |                 iter_1: string[start_idx..].chars(),
    |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected type parameter `I`, found struct `Chars`
    |
    = note: expected type parameter `I`
                       found struct `Chars<'_>`

I can't tell why. std::str::Chars implements the Iterator trait, so my logic was that I could directly assign it to WrappingIter's members. Need I perhaps perform some sort of cast?

CodePudding user response:

This line of code can be roughly translated to "For any type I that implements the Iterator trait, define these methods in the following impl block for WrappingIter<I>" in English.

impl<I> WrappingIter<I> where I: Iterator { /* ... */ }

Notice that the type I can be any type that implements Iterator, but in the new method, an iterator of type std::str::Chars (which is not always equal to the type I) is assigned to it, which is why the code snippet fails to compile.


Here's a solution to make it compile by manually implementing the trait separately for each iterator type. For std::str::Chars, you have to annotate the lifetime explicitly, since the iterator is borrowed from a slice on the argument string. For an alternative solution without generics (and may be more practical), see @Finomnis's answer.

struct WrappingIter<I> {
    iter_1: I,
    iter_2: I,
}

impl<'i> WrappingIter<std::str::Chars<'i>> {
    pub fn new(string: &'i str, start_idx: usize) -> Self {
        Self {
            iter_1: string[start_idx..].chars(),
            iter_2: string[start_idx..].chars(),
        }
    }
}

// for demo purpose, try with another Iterator type
impl WrappingIter<std::ops::Range<i64>> {
    pub fn new(range: std::ops::Range<i64>) -> Self {
        Self {
            iter_1: range.clone(),
            iter_2: range.clone(),
        }
    }
}

CodePudding user response:

In your usecase, using a generic is the wrong approach.

Iterators are meant for the user of your library, to specify a type. In case of a wrapping iterator, why should the user of this iterator have to specify a type? The iterator will always be Chars.

What you do need, however, are lifetime annotations, because Chars borrow from your input string.

use std::str::Chars;

struct WrappingIter<'a> {
    iter_1: Chars<'a>,
    iter_2: Chars<'a>,
}

impl<'a> WrappingIter<'a> {
    pub fn new(string: &'a str, start_idx: usize) -> Self {
        Self {
            iter_1: string[start_idx..].chars(),
            iter_2: string[..start_idx].chars(),
        }
    }
}

impl Iterator for WrappingIter<'_> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        self.iter_1.next().or_else(|| self.iter_2.next())
    }
}

fn main() {
    let s = "abcdefgh";

    let s2 = WrappingIter::new(s, 3).collect::<String>();
    println!("{}", s2);
}
defghabc

However, if you want your WrappingIter to work for more than just Chars, then you do need the generic.

Again, generics are meant for the user of your function to be specified, and in this case the user will specify the type of iterator he passes into this function.

use std::iter::{Skip, Take};

struct WrappingIter<T> {
    iter_1: Skip<T>,
    iter_2: Take<T>,
}

impl<T> WrappingIter<T>
where
    T: Iterator   Clone,
{
    pub fn new(iter_in: T, start_idx: usize) -> Self {
        Self {
            iter_1: iter_in.clone().skip(start_idx),
            iter_2: iter_in.take(start_idx),
        }
    }
}

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

    fn next(&mut self) -> Option<Self::Item> {
        self.iter_1.next().or_else(|| self.iter_2.next())
    }
}

fn main() {
    let s = "abcdefgh";

    let s2 = WrappingIter::new(s.chars(), 3).collect::<String>();
    println!("{}", s2);
}
defghabc

As a little excursion, you can then specify an iterator extension for all iterators:

use std::iter::{Skip, Take};

struct WrappingIter<T> {
    iter_1: Skip<T>,
    iter_2: Take<T>,
}

impl<T> WrappingIter<T>
where
    T: Iterator   Clone,
{
    pub fn new(iter_in: T, start_idx: usize) -> Self {
        Self {
            iter_1: iter_in.clone().skip(start_idx),
            iter_2: iter_in.take(start_idx),
        }
    }
}

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

    fn next(&mut self) -> Option<Self::Item> {
        self.iter_1.next().or_else(|| self.iter_2.next())
    }
}

trait IteratorWrapExt
where
    Self: Sized,
{
    fn wrap(self, start_idx: usize) -> WrappingIter<Self>;
}

impl<T> IteratorWrapExt for T
where
    T: Iterator   Clone,
{
    fn wrap(self, start_idx: usize) -> WrappingIter<Self> {
        WrappingIter::new(self, start_idx)
    }
}

fn main() {
    let s = "abcdefgh";

    let s2 = s.chars().wrap(3).collect::<String>();
    println!("{}", s2);

    let v = [1, 2, 3, 4, 5];
    let v2 = v.iter().wrap(3).collect::<Vec<_>>();
    println!("{:?}", v2);

    // It even works with ranges
    let r = (10..15).wrap(2).collect::<Vec<_>>();
    println!("{:?}", r);
}
defghabc
[4, 5, 1, 2, 3]
[12, 13, 14, 10, 11]

Another quick info:

Wrapping is almost free for every iterator that can jump without cost (like ranges or array iters), but not for strings. Strings have variably-sized elements (a UTF-8 char can have 1 to 4 bytes), and so wrapping requires iterating over the wrapped elements twice.

  • Related