I'm encountering a borrow checker error that I believe should be resolved by Non-Lexical Lifetimes (NLL), but it's failing to compile in Rust 1.88 (2024 Edition). I'd appreciate some insight into whether this is expected behavior or if I'm missing something.
error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
My understanding:
According to NLL, the immutable borrow &bytes[*pos..] should end at the return statement, so self.next() should be able to mutably borrow self since the immutable borrow is no longer live.
Questions:
Is this expected behavior, or should NLL handle this case?
If this is a limitation, is there a specific reason why the borrow checker can't prove safety here?
Are there any known issues or discussions about this pattern?
I've tried various workarounds, but I'm curious about the fundamental reason why this doesn't compile and whether it's considered a soundness issue or just a limitation of the current borrow checker.
I think this is the problem case #4 in the NLL rfc, which is a known issue. this case is tackled in the new polonius borrow checker, which can be enabled with the -Zpolonius compiler flag.
Thanks for the quick reply! I have tried polonius solver and it works!
But it seems that I cannot workaround it with NLL rfc's case #4 (to rename self to self1 in loop), it still errored. So is there any way to work around it?
the workaround mentioned in nll case #4 does not apply to your example, because the you have states being modified that is persistent across method calls, unlike the one in the rfc.
for this particular example, the workaround is simple, just move the return statement out of the loop, something like this:
P.S. this is more or less the same technique as the workaround in the canonical polonius motivating example get_default(map: &'a mut Map, key: &Key) -> &'a mut Value, also noted in problem case #3 in the nll rfc. both incurs redundant work: in the nll case #3, the workaround needs a redundant hash look up for map.contains() and map.insert(), while in this example, it's an extra check of the Option discriminant (hidden inside the Option::unwrap() call).