1

I am trying to make a simple lexer that works on Peekable iterators. When there are no more characters left to iterate I return EOF instead using unwrap_or().

Instead of constantly typing out iter.peek().unwrap_or(&EOF), I have a function peek_or_eof. I try and use the function like this:

use std::iter::Peekable;

const EOF: char = '\0';

enum Token {
    Identifier(String),
}

pub struct Lexer<I>
where
    I: Iterator<Item = char>,
{
    stream: Peekable<I>,
}

impl<I> Lexer<I>
where
    I: Iterator<Item = char>,
{
    fn peek_or_eof(stream: &mut Peekable<I>) -> &char {
        stream.peek().unwrap_or(&EOF)
    }

    fn read_identifier(stream: &mut Peekable<I>) -> Option<Token> {
        // ...

        let mut identifier = String::new();

        let mut next = Lexer::peek_or_eof(stream);
        while next.is_alphanumeric() || next == &'_' {
            identifier.push(stream.next().unwrap());
            next = Lexer::peek_or_eof(stream);
        }

        // ...
        None
    }
}

fn main() {
    println!("Hello, world!");
}

(playground)

The above code results in the error:

error[E0499]: cannot borrow `*stream` as mutable more than once at a time
  --> src/main.rs:31:29
   |
29 |         let mut next = Lexer::peek_or_eof(stream);
   |                                           ------ first mutable borrow occurs here
30 |         while next.is_alphanumeric() || next == &'_' {
31 |             identifier.push(stream.next().unwrap());
   |                             ^^^^^^ second mutable borrow occurs here
...
37 |     }
   |     - first borrow ends here

If I understand correctly, the borrow lifetime is the same as the returned character reference, which in this case is next. However, I don't actually use next after checking the condition in the while loop and next will be overwritten with a new value before the next iteration of the loop.

Am I making a larger mistake? How do I let the compiler know that the mutable borrow on stream is done and that it is safe to allow another mutable borrow?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Increasingly Idiotic
  • 5,700
  • 5
  • 35
  • 73
  • 1
    Also, why would you return a `&char` rather than a `char`? – mcarton Jul 29 '18 at 14:36
  • @mcarton [`peek()`](https://doc.rust-lang.org/std/iter/struct.Peekable.html#method.peek) returns a reference to the next item in the iterable. It cannot take ownership of the actual item as it is only peeking ahead, not actually consuming the value like `next()` would. To return a `char` the char would have to be cloned first. – Increasingly Idiotic Jul 29 '18 at 16:51
  • @SilvioMayolo Can you share what you have that compiles without borrow errors? I'm new and could benefit from seeing code from someone more familiar with Rust. – Increasingly Idiotic Jul 29 '18 at 16:52
  • [The duplicate applied to your question](https://play.rust-lang.org/?gist=e1f7e6d94aebc976f96264c517041d17&version=stable&mode=debug&edition=2015). – Shepmaster Jul 29 '18 at 16:54
  • Upon going back to it today, I realize I made a foolish typo that confused the compiler. I can reproduce your error now. But I think mcarton is right that you should just clone the `char`. Cloning characters is cheap, and retaining a reference to that character in the container is what's stopping you from doing what you want to do. – Silvio Mayolo Jul 29 '18 at 16:55
  • 3
    @IncreasinglyIdiotic but `char` is a trivial type to clone/copy, so unless you really do need a reference, don't use a reference, and I don't believe a reference would ever be useful here. – mcarton Jul 29 '18 at 16:57
  • @SilvioMayolo Thanks, but what if I have the same issue with something else that cannot be trivially cloned? – Increasingly Idiotic Jul 29 '18 at 16:57
  • 3
    Your [original code will work as-is](https://play.rust-lang.org/?gist=7001578b86c14610d08b533cbe531980&version=nightly&mode=debug&edition=2015) once [non-lexical lifetimes](https://stackoverflow.com/q/50251487/155423) are available. – Shepmaster Jul 29 '18 at 16:57
  • @Shepmaster I think that clears things up quite a bit, thanks! Am I right in understanding the code above cannot be compiled by rust without cloning the char as long as Rust relies on lexical lifetimes? – Increasingly Idiotic Jul 29 '18 at 17:03
  • 1
    That's correct. The compiler currently doesn't see that the borrow held by `next` is no longer used once the while loop's body is started. – Shepmaster Jul 29 '18 at 17:07

0 Answers0