I want to make an iterator that owns another iterator and gives items based on that other iterator. Originally, the inner iterator is formed from the result of a database query, it gives the raw data rows as arrived from the DB. The outer iterator takes items of the inner iterator and puts them into a struct that is meaningful within my program. Because different software versions store the same data in different database table structures, I have a parser trait that takes a row and creates a structure. My outer iterator takes two parameters for creation: the iterator for the DB rows and an object which implements how to parse the data.
But I run into a lifetime error which I don't really see the reason of, and following the compiler's hints only lead me in circles. I literally follow the compiler's advice and getting back to the same problem. I tried to minic the code and bring it to a minimal form to reproduce the same compiler errors I'm getting. I'm not entirely sure if it could be minimized further, but I also wanted it to resemble my real code.
Here is the sample:
struct Storeroom<'a> {
storeroom_id: i64,
version: &'a str
}
trait StoreroomParser {
fn parse(&self, row: Row) -> Result<Storeroom, Error>;
}
struct StoreroomParserX;
impl StoreroomParser for StoreroomParserX {
fn parse(&self, row: Row) -> Result<Storeroom, Error> {
Ok(Storeroom { storeroom_id: row.dummy, version: "0.0.0"})
}
}
struct StoreroomIterator {
rows: Box<dyn Iterator<Item = Row>>,
parser: Box<dyn StoreroomParser>
}
impl StoreroomIterator {
fn new() -> Result<Self, Error> {
let mut rows: Vec<Row> = vec![];
rows.push(Row { dummy: 4});
rows.push(Row { dummy: 6});
rows.push(Row { dummy: 8});
let rows = Box::new(rows.into_iter());
let parser = Box::new(StoreroomParserX {});
Ok(Self {rows, parser})
}
}
impl Iterator for StoreroomIterator {
type Item<'a> = Result<Storeroom<'a>, Error>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(nextrow) = self.rows.next() {
Some(self.parser.parse(nextrow))
}
else {
None
}
}
}
During my first attempt, the compiler suggested to add a lifetime annotation to the Item type declaration, because it uses a struct that requires a lifetime. But this resulted in the following error:
error[E0658]: generic associated types are unstable
--> src/main.rs:59:5
|
59 | type Item<'a> = Result<Storeroom<'a>, Error>;
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: see issue #44265 <https://github.com/rust-lang/rust/issues/44265> for more information
error[E0195]: lifetime parameters or bounds on type `Item` do not match the trait declaration
--> src/main.rs:59:14
|
59 | type Item<'a> = Result<Storeroom<'a>, Error>;
| ^^^^ lifetimes do not match type in trait
Here's the sample code on Playground.
When I tried to mitigate this by moving the lifetime annotation to the impl block instead, I provoked the following error I can't progress from:
error: lifetime may not live long enough
--> src/main.rs:61:13
|
56 | impl<'a> Iterator for StoreroomIterator<'a> {
| -- lifetime `'a` defined here
...
59 | fn next(&mut self) -> Option<Self::Item> {
| - let's call the lifetime of this reference `'1`
60 | if let Some(nextrow) = self.rows.next() {
61 | Some(self.parser.parse(nextrow))
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
I've been stuck on this problem for about a week now. Do you have any ideas how to resolve these errors? I'm also thinking that I should probably just use map()
on the rows with whatever closure it takes to properly convert the data, but at this point it would definitely feel like a compromise.