Rust newbie here! Can you help me understand what is wrong with the following code? It is a contrived example that I think is the MRE of the problem that I am having in my actual code.
enum LinkedList {
Sentinel,
Nonempty {
remaining: Box<LinkedList>,
value: i32,
},
}
impl LinkedList {
fn f(&mut self) {
let mut current = self;
let mut count = 1;
while let LinkedList::Nonempty { remaining, .. } = current {
if count > 1 {
break;
}
current = remaining;
count += 1;
}
*current = LinkedList::Sentinel;
}
}
Trying to run this block yields:
error[E0506]: cannot assign to `*current` because it is borrowed
--> src/main.rs:20:9
|
13 | while let LinkedList::Nonempty { remaining, .. } = current {
| --------- `*current` is borrowed here
...
20 | *current = LinkedList::Sentinel;
| ^^^^^^^^
| |
| `*current` is assigned to here but it was already borrowed
| borrow later used here
The error message was confusing to me: *current is borrowed but that borrow shouldn't extend beyond while let.
If I remove the if block within the while let statement, it runs just fine. So it seems like the break statement is causing the problem but it is not clear to me how.
The borrow checker is, currently,[1] bad at reasoning about conditional control flow (if ... break) that results in an exclusive (&mut) borrow being either used (current = remaining) or dropped (break). The borrow that is problematic here is the implicit borrow of remaining from current. Under these circumstances, the borrow checker erroneously concludes that the conditionally used borrow is always used — it acts as if remaining is still borrowed when the loop exits.
The way to avoid this problem is to organize your code so that the problematic exclusive borrow is not taken until it is certainly going to be used.
It’s hard to say how best to do this so as to solve your real problem, because as written, your loop only loops at most once, and the break could be just put at the end of the loop (or the while made into an if). But if I try to preserve the complex conditionality and not simplify anything, what comes to mind is using a loop { match with two arms for the break case and the non-break case:
impl LinkedList {
fn f(&mut self) {
let mut current = self;
let mut count = 1;
loop {
match current {
LinkedList::Nonempty { .. } if count > 1 => break,
LinkedList::Nonempty { remaining, .. } => {
current = remaining;
count += 1;
}
_ => break,
}
}
*current = LinkedList::Sentinel;
}
}
While in this code the first match arm does not borrow remaining at all, it would also be okay if it did, because it would still be a different borrow, with a different lifetime, than the one which is later assigned to current.
It probably won't be solved by the first version of Polonius likely to land on stable (it still errors with -Zpolonius=next). It probably will be solved by a "complete" version of Polonius, if/when we get that (it compiles with -Zpolonius=legacy).
I was actually trying to implement a delete method for a binary search tree and while traversing the tree, I needed to peek ahead to figure out when to terminate traversal.
I ended up implementing delete with two passes over the tree, with the first pass just finding out the exact path that will lead to the node that needs to be operated on. In the second pass my code only borrows the nodes that it needs to use as suggested.