Put a mutable iterator inside a struct

I'm trying to implement a simple string parser by iterating over each character. To keep track of line and column number, I'm storing the iterator inside a struct. Here's my attempt:

struct ParseState<'a> {
  iter: &'a mut Chars<'a>
, peeked_char: Option<char>
, num_line: u32
, num_char: u32
}

impl ParseState<'_> {
  fn new(s: &String) -> ParseState {
    ParseState { iter: &mut s.chars()
               , peeked_char: None
               , num_line: 0
               , num_char: 0
               }
  }

  fn peek(&mut self) -> Option<char> {
    if !self.peeked_char.is_some() {
      self.peeked_char = self.next();
    }
    self.peeked_char
  }
}

impl Iterator for ParseState<'_> {
  type Item = char;

  fn next(&mut self) -> Option<Self::Item> {
    if self.peeked_char.is_some() {
      let c = self.peeked_char;
      self.peeked_char = None;
      return c
    }
    let c = self.iter.next();
    if c.is_some() {
      self.num_char += 1;
      if c.unwrap() == '\n' {
        self.num_line += 1;
        self.num_char = 0;
      }
    }
    c
  }
}

But it fails to compile:

warning: unused variable: `m`
  --> src/lib.rs:15:7
   |
15 |   let m = input_parsers::parse_satlib(&s);
   |       ^ help: if this is intentional, prefix it with an underscore: `_m`
   |
   = note: `#[warn(unused_variables)]` on by default

error[E0515]: cannot return value referencing temporary value
  --> src/input_parsers.rs:17:5
   |
17 |       ParseState { iter: &mut s.chars()
   |       ^                       --------- temporary value created here
   |  _____|
   | |
18 | |                , peeked_char: None
19 | |                , num_line: 0
20 | |                , num_char: 0
21 | |                }
   | |________________^ returns a value referencing data owned by the current function
   |
   = help: use `.collect()` to allocate the iterator

For more information about this error, try `rustc --explain E0515`.
warning: `erisat` (lib) generated 1 warning
error: could not compile `erisat` due to previous error; 1 warning emitted

I know it has to do with borrowed ownership, but I'm a complete Rust newbie so I'm unable to know what to do to work around this.

You probably want to move ownership of the iterator into your structure:

use core::str::Chars;
struct ParseState<'a> {
    iter: Chars<'a>,
    peeked_char: Option<char>,
    num_line: u32,
    num_char: u32,
}

impl ParseState<'_> {
    fn new(s: &String) -> ParseState {
        ParseState {
            iter: s.chars(),
            peeked_char: None,
            num_line: 0,
            num_char: 0,
        }
    }

    fn peek(&mut self) -> Option<char> {
        if !self.peeked_char.is_some() {
            self.peeked_char = self.next();
        }
        self.peeked_char
    }
}

impl Iterator for ParseState<'_> {
    type Item = char;

    fn next(&mut self) -> Option<Self::Item> {
        if self.peeked_char.is_some() {
            let c = self.peeked_char;
            self.peeked_char = None;
            return c;
        }
        let c = self.iter.next();
        if c.is_some() {
            self.num_char += 1;
            if c.unwrap() == '\n' {
                self.num_line += 1;
                self.num_char = 0;
            }
        }
        c
    }
}
4 Likes

If you're new to Rust, don't ever put temporary references inside structs. This is an advanced feature that does not do what you assume. It doesn't store data, it doesn't store it "by reference". It makes the outer struct temporary and bound to a scope, which in almost all cases is undesirable and complicating everything.

To store something "by reference" (a pointer), use Box<…>, not &, and remember that everything owned is mutable, so &mut is not necessary to make things mutable. It's used to make loans exclusive and temporary.

8 Likes

Thanks! That's exactly what I was looking for!

This is good advice; I'll keep that in mind. Thanks!