Can we use `iter.next()` inside a function in a `while let Some(i) = iter.next() {}` loop

I have a project where I read an Excel file and serialize it to JSON. (By the way thank you very much to the peoples doing the calamine crate and certainly serde will be next).

I am at a point where I want to parse multiples lines into one object (cells for two items are as follow):

ItemType A ID 42
Name Foo
Property A Bar
Property Z Bar
ItemType B ID 43
Name Baz
Property B Toto Titi Tata
/* Next item, there is a full list */

I wanted to have a function that parse the catalog and detect which type of element is there (A or B) and call the corresponding struct's new method. In this method I need the current line (to get the id) and an
I just learned about the while let Some(row) = rows.next() {} syntax and it looked exactly what I wanted.

Except that I cannot pass rows to a function so that function is able to call rows.next() itself. Or at least I did not find a way to do it that please the compiler.

pub struct Catalog {
    range: Range<DataType>,
}

pub fn parser(&self) {

let mut rows = self.range.rows();
while let Some(row) = rows.next() {
    let label = match &row[0] {
        DataType::String(str) => str,
        _ => "Not a string",
    };

    if label == "ItemTypeA" {
        // if I do rows.next() here, the compiler is happy
        // if I call it inside of ItemTypeA::new I cannot find any way to make the compiler happy
        items::Item::ItemTypeA(items::ItemTypeA::new(&row[1], rows));
    } else if label == "ItemTypeB" {
        items::Item::ItemTypeB(items::ItemTypeB::new(&row[1], rows));
    } else {
        eprintln!("Skipping unknown item type: {}", label);
    }
}
}

pub mod items {
    use calamine::{DataType, Rows};

    pub struct ItemTypeA {
        id: u64
        name: String
        propertyA: String
        propertyZ: String
    }

    impl ItemTypeA {
        // Which types should be written there ?
        pub fn new(row: &DataType, rows: &Rows<DataType>) -> Self {
            dbg!(rows.next()) // trigger a compile time error.
            ItemTypeA {}
        }
    }

    // impl the same for ItemTypeB
}

Is it normal and I just forgot something ? Or did not read the book thoroughly enough ?
Is there any better way to do it ? My workaround is creating a temporary struct in the if/else branches but I do not really like it as it split the parsing. And the parse function may grow exponentially if we add items types:

if label == "ItemTypeA" {
    let item = items::ItemTypeB {
        id: vec![row[1].clone()],
        name: vec![], // Do rows.next() and pass it in here
        propertyB: vec![], // Do rows.next() and pass it in here
    };
    // Then call new with the
    let item = items::Item::ItemTypeB(items::ItemTypeB::new(item));

If you have a immutable reference to an iterator (such as &Rows<DataType>), you cannot call next on it. Advancing an iterator mutates it, and you can't mutate something through an immutable reference. Try changing pub fn new(row: &DataType, rows: &Rows<DataType>) to pub fn new(row: &DataType, rows: &mut Rows<DataType>).

I'd also recommend putting together a runnable example on play.rust-lang.org if possible, it makes it a lot easier for people to help/suggest fixes :slight_smile:

2 Likes

Thanks for your response, here is a link to the compiling version (no .next() in new()).

If you change the comment state of lines after // /!\ Uncomment this line the version I cannot make to compile is shown (Rust Playground).

However in the play rust extract the error is the following:

   Compiling playground v0.0.1 (/playground)
error[E0382]: borrow of moved value: `rows`
  --> src/main.rs:27:31
   |
26 |         let mut rows = self.range.iter();
   |             -------- move occurs because `rows` has type `std::slice::Iter<'_, std::string::String>`, which does not implement the `Copy` trait
27 |         while let Some(row) = rows.next() {
   |                               ^^^^ value borrowed here after move
...
33 |                 items::ItemTypeA::new(row.to_string(), rows);
   |                                                        ---- value moved here, in previous iteration of loop

error: aborting due to previous error

For more information about this error, try `rustc --explain E0382`.
error: Could not compile `playground`.

To learn more, run the command again with --verbose.

And I think I did not saw the last part on my side :confounded: :

which does not implement the `Copy` trait

But I want the parser() iterator to advance when the called function call next internally.

As @17cupsofcoffee said, you have to use a mutable reference. Something like this.
If you take the iterator by value it will be moved at the first iteration (since it's not Copy) and the loop won't be able to access it anymore (that's what the compiler is complaining about).

Alternatively, you could clone the iterator. Because this iterator is one for a slice, that will just clone 2 pointers.

Thank you everyone, having a working example is nice when your type is from someone else crate. And I totally forgot that we can/should add &mut when calling a function.

So to get it work with the calamine crate I needed to call with this line:

items::ItemTypeA::new(&row[1], &mut rows); // Note: thanks you for the reminder of &mut here

And the new function prototype is the following one:

pub fn new(row: &DataType, rows: &mut Rows<DataType>) -> Self { /* impl */ } 

Note that I mistakenly wanted to write the following:

pub fn new(row: &DataType, &mut rows: Rows<DataType>) -> Self { /* impl */ } // Note: &mut is not at the right place.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.