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.
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 ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With