2

I'm having an issue writing a Lexical Analyzer in Rust where certain functions are starting to complain about simple snippets that would otherwise appear harmless. This is starting to become an annoyance as the error messages are not helping me pinpoint the cause of my problems and so this is the second time I'm reaching out on the same program in the same week (previous question here).

I have read the book, I've understood everything I could from it. I've also watched/read numerous other articles and videos discussing lifetimes (both explicit and implicit) and for the most part the concept behind borrowing and moving make perfect sense, except in cases like the following:

My lexer has a next function who's purpose is to peek ahead at the next character and return it.

struct Lexer<'a> {
    src: str::Chars<'a>,
    buf: String,
    // ... not important
}

impl<'a> Lexer<'a> {
    // ... not relevant

    // originally this -> Option<&char> which caused it's own slew of problems
    // that I thought dereferencing the character would solve.
    fn next(&self) -> Option<char> {
        let res = self.src.peekable().peek();
        // convert Option<&char> to Option<char>
        match res {
            Some(ref c) => Some(*c.clone()),
            None => None
        }
    }

    // ... not relevant
}

The error that I'm getting when doing this is:

error: borrowed value does not live long enough
       let res = self.src.peekable().peek();
                 ^~~~~~~~~~~~~~~~~~~

What I understand from this error is that the value from peekable() is not living long enough, which kind of makes sense to me. I'm only referencing the return in that line and calling another function which I imagine is returning a pointer to the character at the next location with the iterator. My naive solution to this was:

let mut peeker = self.src.peekable();
let res = peeker.peek();

If I implement this solution, I see a different error which also does not make sense to me:

error: cannot move out of borrowed content
       let mut peeker = self.src.peekable();
                        ^~~~

I'm not quite sure what's moving self out of a borrowed context here (I know it's borrowed from &self but not sure what's moving it out of the borrowed context.

EDIT

I proposed a question with details that were wildly inaccurate. The portion of the post that contained those details has been updated with actual facts - I mixed up two different situations where I had encountered a similar error (similar to me, at least).

Community
  • 1
  • 1
Brandon Buck
  • 7,177
  • 2
  • 30
  • 51
  • Are you sure the peeker error has the same error message? – bluss Apr 22 '15 at 18:33
  • No, I must have confused that with something else. – Brandon Buck Apr 22 '15 at 18:42
  • 1
    .peekable() is an adaptor, so it tries to move out `self.src` and embed it in itself, giving you a new iterator. It doesn't look like this is what you want. You might even .clone() the Chars iterator instead if you want to look ahead. – bluss Apr 22 '15 at 18:44
  • 1
    @user139873 The answer has been updated to reflect what actually happens. I apologize for that. – Brandon Buck Apr 22 '15 at 18:45

1 Answers1

5

Let's start with the second error.

As mentioned in the comments, Iterator::peekable is an iterator adapter that consumes the iterator it is going to make peekable. A small reproduction:

let values = [1,2,3];
let mut iterator = values.iter();
iterator.peekable(); // Consumes `iterator`
iterator.next(); // Not available anymore!

You can use Iterator::by_ref to get a reference that can then itself be consumed. Note that the underlying iterator will still be advanced!

let values = [1,2,3];
let mut iterator = values.iter();
iterator.by_ref().peekable();
iterator.next();

In your case, you are trying to consume the value out of a borrowed struct (via &self), which has the specific error cannot move out of borrowed content.


Let's look at your original error:

let values = [1,2,3];
let next = values.iter().peekable().peek();

The problem here is that peek returns a reference. This makes sense, as we don't know if the item we are iterating over is Copyable. However, that reference has to have somewhere to live. That place to live is the Peekable iterator itself! Peekable allocates enough space to store the "next" element. When you call peek, it advances the underlying iterator, stores the value, then returns the reference. Check out the function signature for peek to see this captured in code:

fn peek(&mut self) -> Option<&I::Item>

You can re-add the lifetimes to be explicit:

fn peek<'a>(&'a mut self) -> Option<&'a I::Item>

In the one-line version, you create and then destroy the Peekable, so there's nowhere for the value to live, so the reference dies in the same statement.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • I'm glad that I understood why the original wouldn't work. Although ins't consumption of a variable like with `peekable` consuming it's iterator that called the function kind of a weird side effect? It's not something I'd ever expect (to call a random function and lose the variable I called from). That would not have been something I would have thought of. The `by_ref` mentioned though seems to be what I want. I want a living `Peekable` into my iterator so that I can consume and peek. The peeking is used to determine if the lexer will continue with the next character for the current token. – Brandon Buck Apr 22 '15 at 19:26
  • 1
    @BrandonBuck I wouldn't call it a *side effect* - you are simply moving the iterator by-value into another iterator. That's why the [function signature](http://doc.rust-lang.org/std/iter/trait.Iterator.html#method.peekable) shows the argument as `self` not `&self`. That's a strong benefit of Rust's ownership model, and you'll learn to love it for the performance and explicitness. – Shepmaster Apr 22 '15 at 19:29
  • It's frustrating occasionally when I expect things to be safe that Rust doesn't see the same way. – Brandon Buck Apr 22 '15 at 19:30
  • 1
    @BrandonBuck I think maybe we are using different meanings of *safe*. Rust allows you give ownership of your iterator to the iterator adapter, and then prevents you from accidentally using it when you don't own it. It also allows you to hand out a reference to your owned value and ensures that *that* reference isn't used after it expires. There's no unsafety here! – Shepmaster Apr 22 '15 at 19:44
  • I'm totally for the reference method. That's the exact functionality I was trying to duplicate. I guess my confusion is that calling a method does not imply to me that I'm giving away ownership. If i passed the operator into a method, like `Peekable::new(iterator)` that ownership transfer is more intentional (on my side, as the consumer of the API). And by that I mean implicit ownership transfer (calling a method and losing ownership of the object that called the method) vs explicit (passing the object as an argument). – Brandon Buck Apr 22 '15 at 22:54
  • 1
    @BrandonBuck there's nothing *particularly* special about methods vs functions. If the way it looks bothers you, you can use Universal Function Call Syntax (UFCS) and [call it via `Iterator::peekable`](http://is.gd/QlB0ty). However, I think that the method version is going to be more common and idiomatic. – Shepmaster Apr 23 '15 at 00:42
  • I suppose that is where my confusion was coming from. I didn't realize that `iterator.peekable()` was sugar for `Iterator::peekable(iterator)`, but it makes perfect sense. I think, IMHO, that the latter is more meaningful in demonstrating the outcome while the former lacks the communication of transferring ownership. This is more of an API design decision, by that I mean I would have made `peekable` take an iterator and not operate on self. – Brandon Buck Apr 23 '15 at 02:47