I have created a repo with the current state of my code:
This method:
Needs to return a mutable reference, &mut DataNode, because it will be used by another method, that is upsert(), which adds new nodes into the DataNode. But, the compiler complains, because I need to call methods by using the same &mut self within the same method at #92 and #97, above.
This long-standing problem is known as “NLL Problem Case #3”. The borrow checker has difficulty with the combination of
taking a mutable borrow, then based on a condition, either
returning it, or
dropping it and taking a new mutable borrow to the same place.
The workaround is to change one of these things — usually, by avoiding taking a mutable borrow before the condition.
In your case, I would suggest changing locate_range_key so that it returns an index into self.children instead of a reference. This way, it doesn't need to take a mutable borrow, and so query_datanode can make its decision about which of its three branches it follows before any of the possibly-returned mutable borrows are taken.
Thanks Kevin. You're saying every time I use the self reference in a method, which is of type "&mut InnerData" and passed as a method argument, it's a new mutable borrow, and the compiler cannot know if the first borrow's lifetime is over (because it doesn't run the code), and therefore it doesn't permit a new borrow later?
fn query_thru_sibling(&mut self, query_index_key: &Query) -> &mut DataNode {
// vvvv (a)
match &mut self.node.sibling {
Some(s) => match s {
// (b) vvvvvvvvvvvvvvvvvvv.....
TreeNode::INNER(i) => i.query_datanode(query_index_key),
_ => panic!("Not expected to find a data node sibling!"),
},
// Sibling doesn't exist, so we reached the most right branch of the tree.
None => self.query_thru_last(query_index_key),
}
}
...the borrow at (a) is effectively returned[1] at (b), so the compiler concludes that the exclusive borrow at (a) lasts for the rest of the method body.[2] That's why it's saying it's "borrowed as mutable" (exclusively borrowed) in the None branch.
In order for this to not happen, we need more flow-sensitive lifetimes, so that the lifetime is greater than the method body in the Some branch but not the None branch. (The plan is that the next-generation borrow checker, Polonius, will implement this.)
Incidentally, assuming sending the same query twice in a row gives the same result, this signature is unworkable:
let one = inner.query_thru_last(&query);
let two = inner.query_thru_last(&query);
use_both(one, two);
But having two &mut _ to the same place usable at the same time is undefined behavior.
the lifetime has to be as long as the return type lifetime ↩︎
The caller chose the lifetime on the &mut self parameter, and thus on the return type, and caller-chosen lifetimes are always a strict superset of the function body. ↩︎
I am a bit confused.
How many borrows are there, actually? I thought when I call a method on a struct instance, it is the first borrow which is passed to the method. The second borrow happens when I try to access the sibling, &mut self.node.sibling by using the self. So, I effectively try to obtain a mutable reference to sibling at A. Shouldn't the borrow checker jump in at A and complain there?
If I understand, we're talking about reborrows. The borrow checker doesn't complain because the &mut self is inactive for the duration of the reborrow.
In brief, the point of reborrowing is that if you have a &'long mut T, you can get another &'short mut T, but only if it's derived from the original, longer borrow. This is because in this case, the compiler can temporarily "freeze" the longer borrow for the duration of the shorter one (since they are related). Thereby it still ensures that no simultaneous mutation actually happens (even though the two mutable references do technically alias), but it also allows "temporary" mutable sub-borrows instead of consuming (and thus rendering unusable) the original borrow.
Reborrows are necessary because without reborrows, there would be no way to write functions like this:
If reborrowing didn't exist, the first call to Vec::push() would move the mutable reference, and the second call would cause a compile error.
The compiler could not perform such "temporary freezing" if the two mutable references weren't derived from each other, which would lead to concurrent mutation and UB.