Home > Software design >  How to accept str.chars() or str.bytes() in a function and iterate twice?
How to accept str.chars() or str.bytes() in a function and iterate twice?

Time:07-14

Is there any way to pass somestring.chars() or somestring.bytes() to a function and allow that function to reconstruct the iterator?

An example is below. The goal is for the function to be able to iterate through coll multiple times, reconstructing it as needed using into_iter(). It works correctly for vectors and arrays, but I have not been able to get it working for the string iterator methods.

// Lifetime needed to indicate the iterator objects
// don't disappear
fn test_single<'a, I, T>(collection: &'a I)
where
    &'a I: IntoIterator<Item = T>,
    T: Display,
{
    let count = collection.into_iter().count();
    println!("Len: {}", count);

    for x in collection.into_iter() {
        println!("Item: {}", x);
    }
}

fn main() {
    // Works
    test_single(&[1, 2, 3, 4]);
    test_single(&vec!['a', 'b', 'c', 'd']);

    let s = "abcd";

    // Desired usage; does not work
    // test_single(&s.chars());
    // test_single(&s.bytes());
}

The general error is that Iterator is not implemented for &Chars<'_>. This doesn't make sense because chars definitely does implement IntoIterator and Iterator

Is there a solution that allows for the desired usage of test_single(&s.chars())?

Link to the playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=230ee86cd109384a1c62c362aed9d47f

(IntoIterator is prefered over Iterator for my application, since I also need to specify that IntoIterator::IntoIter is a DoubleEndedIterator.)

CodePudding user response:

This can work but not the way you have it written.

You can't iterate a shared reference because Iterator::next() takes &mut self. IntoIterator::into_iter() could be made to work with e.g. &Chars, but that's not necessary because Chars and Bytes both implement Clone, which creates a copy of the iterator (but doesn't duplicate the underlying data).

So you just need to adjust your bounds and accept the iterator by value, cloning it when you will need another iterator later:

fn test_single<I, T>(collection: I)
where
    I: Clone   IntoIterator<Item = T>,
    T: Display,
{
    let count = collection.clone().into_iter().count();
    println!("Len: {}", count);

    for x in collection.into_iter() {
        println!("Item {}", x);
    }
}

Now you can call test_single(s.chars()), for example.

(Playground)


Side note: You can express the type I purely with impl, which might be more readable:

fn test_single(collection: impl Clone   IntoIterator<Item=impl Display>) {
  • Related