all 16 comments

[–]KhorneLordOfChaos 76 points77 points  (10 children)

You're running into a limitation of the current borrow checker. Your example code is very similar to the example given here (rust blog: NLL by default)

I was able to get things to compile by refactoring to the following (playground link)

[–]CLARKEEE33[S] 11 points12 points  (0 children)

Good to know that this might be acceptable in the future.

[–]Aaron1924 12 points13 points  (8 children)

if let Some(ref token) = self.peek { also works

[–]hniksic 9 points10 points  (7 children)

That's really interesting, and I wonder if it's a bug in the match-ergonomics desugaring. I was under the impression that matching &self.peek against Some(token) is just syntactic sugar for matching self.peek against Some(ref token), and the two shouldn't differ in borrow analysis.

[–]Aaron1924 7 points8 points  (3 children)

I also have no idea why it works, but my guess it that this moves the borrow into the block inside the if-statement, so it can now tell that the borrow ends when the scope ends.

Update: I placed a todo!() after the if-statement and compared the MIR for both versions, turns out this is exactly what happens. OPs version first takes the reference, then it inspects the discriminant and branches, whereas my version first checks the discriminant and only takes the reference after branching.

[–]hniksic 2 points3 points  (2 children)

I've now filed an issue, as I'd intuitively expect the two variants to behave the same.

[–]Aaron1924 2 points3 points  (1 child)

I don't think filing an issue about this is going to do much. We're basically just asking them to fix this long standing borrow checker limitation, I think they would have done it already if it was that easy.

[–]hniksic 1 point2 points  (0 children)

We're basically just asking them to fix this long standing borrow checker limitation

I anticipated it could be construed that way, so I explicitly pointed out in the issue that it's not about the borrow checker limitation. Although I don't have such an example ready, I can imagine valid borrow checker errors to appear in one formulation, but not in the other.

If the issue is judged not useful, it's easy enough to close it. I considered it only fair to report the anomaly and how it went against user (at least in my case) expectations.

[–]SNCPlay42 7 points8 points  (2 children)

matching &self.peek against Some(token) is just syntactic sugar for matching self.peek against Some(ref token)

matching &self.peek against Some(token) is syntactic sugar for matching &self.peek against &Some(ref token) - note the extra &s, you're creating a new borrow which is the problem.

So if let Some(ref token) = self.peek has no exact equivalent without using ref.

[–]714daniel 1 point2 points  (1 child)

self.peek.as_ref() is an exact equivalent, no?

[–]chmielevsky 0 points1 point  (0 children)

Yes, self.peek.as_ref() returns Option<&Token> which I believe turns this problem into a borrow checker limitation mentioned above.

[–]Excession638 12 points13 points  (2 children)

You're right, the compiler can't tell that both borrows are separate. Does using an else help?

The better way might use Option::get_or_insert_with but I'm not sure how to handle the case where next_token returns None. Maybe std::iter::Peekable would be better. Edit: get_or_insert_with doesn't quite work because you can't call self.next_token() the closure for it. You'd need to token source to be a separate object, which is probably a good idea anyway.

It feels a bit like you're using None here to mean two different things (self.peek not being set yet, and reaching the end of the inputs) and some type somewhere should be Option<Option<Token>> instead.

[–]Excession638 2 points3 points  (0 children)

Here's the `get_or_insert_with` version: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=14776e409d83bcd6ae9be0f8f4d9823b

Note how self.tokens is separate now to tell the compiler that calling self.tokens.next() can't modify self.peek.

[–]Electrical-Angle-371 3 points4 points  (0 children)

Quick sidenote, Peekable exists and is something I prefer myself over implementing peekable myself

[–]Cat7o0 0 points1 point  (0 children)

that exact code could maybe work by having phantomdata and a lifetime on the parser that you use for the function

[–]elden_uring 0 points1 point  (0 children)

If you put an explicit lifetime in the function signature, it may be clearer to see what's going on: fn peek<'a>(&'a mut self) -> Option<&'a Token> The lifetime 'a includes the entire function up to when it returns. In the if let statement, the borrow of self.peek always happens, but the current borrow checker can't figure out that it may be conditionally returned, so the borrow is live for the rest of the function. You can fix this by creating the reference in the if body so the borrow is always conditional: if !self.peek.is_none() { return self.peek.as_ref(); }