Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Wrong number of lifetime parameters when using a modified `Chars` iterator

I want to implement the IntoIterator trait for a struct containing a String. The iterator is based on the chars() iterator, is supposed to count the '1' chars and accumulate the result. This is a simplified version of what I got so far:

use std::iter::Map;
use std::str::Chars;

fn main() {
    let str_struct = StringStruct { system_string: String::from("1101") };
    for a in str_struct {
        println!("{}", a);
    }
}

struct StringStruct {
    system_string: String
}

impl IntoIterator for StringStruct {
    type Item = u32;
    type IntoIter = Map<Chars, Fn(char) -> u32>;

    fn into_iter(self) -> Self::IntoIter {
        let count = 0;
        return self.system_string.chars().map(|c| match c {
            Some('1') => {
                count += 1;
                return Some(count);
            },
            Some(chr) => return Some(count),
            None => return None
        });
    }
}

Expected output: 1, 2, 2, 3

This fails with:

error[E0107]: wrong number of lifetime parameters: expected 1, found 0
  --> src/main.rs:17:25
   |
17 |     type IntoIter = Map<Chars, Fn(char) -> u32>;
   |                         ^^^^^ expected 1 lifetime parameter

The chars iterator should have the same lifetime as the StringStruct::system_string, but I have no idea how to express this or if this approach is viable at all.

like image 511
webbertee Avatar asked Oct 15 '16 10:10

webbertee


1 Answers

To answer the question you asked, I'd recommend to impl IntoIterator for &StringStruct (a reference to a StringStruct instead of the struct directly). The code would look like this:

impl<'a> IntoIterator for &'a StringStruct {
    type Item = u32;
    type IntoIter = Map<Chars<'a>, Fn(char) -> u32>;
    // ...
}

However, you will notice many more errors that have a different origin afterwards. The next error that pops up is that Fn(char) -> u32 does not have a constant size at compile time.

The problem is that you try to name the type of your closure by writing Fn(char) -> u32. But this is not the type of your closure, but merely a trait which is implemented by the closure. The type of a closure can't be named (sometimes called "Voldemort type").

This means that, right now, you can't specify the type of a Map<_, _> object. This is a known issue; the recently accepted impl Trait-RFC might offer a workaround for cases like this. But right now, it's not possible, sorry.

So how to solve it then? You need to create your own type that implements Iterator and use it instead of Map<_, _>. Note that you can still use the Chars iterator. Here is the full solution:

struct StringStructIter<'a> {
    chars: Chars<'a>,
    count: u32,
}

impl<'a> Iterator for StringStructIter<'a> {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
         self.chars.next().map(|c| {
            if c == '1' {
                self.count += 1;
            }
            self.count
        })
    }
}

impl<'a> IntoIterator for &'a StringStruct {
    type Item = u32;
    type IntoIter = StringStructIter<'a>;

    fn into_iter(self) -> Self::IntoIter {
         StringStructIter {
             chars: self.system_string.chars(),
             count: 0,
         }
    }
}

fn main() {
    let str_struct = StringStruct { system_string: String::from("1101") };
    for a in &str_struct {
        println!("{}", a);
    }
}

And just a small note: an explicit return when not necessary is considered bad style in Rust. Better stick to rule and write idiomatic code by removing return whenever possible ;-)

like image 173
Lukas Kalbertodt Avatar answered Nov 19 '22 18:11

Lukas Kalbertodt