Edit: Just reread the NLL proposal, and saw this was problem case #4. I’d forgetten this was addressed there.
When borrowing through mutable reference stored in a variable, you have two options for how to treat the variable, either moving out of it or reborrowing it. If you intend to reassign the variable while the loan exists, you have to move out of the variable, and otherwise you typically reborrow the variable so you can continue to use it after the loan expires. However, in some cases, you want to conditionally reassign the variable. If you move out of the variable, then it becomes invalid in the case where you don’t reassign it. If you reborrow it, then you can’t reassign it while the reborrow exists. Consider the case where you’re traversing a simple linked list, such as to get a reference to the foot:
struct Node {
value: i32,
next: Option<Box<Node>>,
}
impl Node {
fn get_next_mut(&mut self) -> &mut Option<Box<Node>> {
&mut self.next
}
}
fn cons(value: i32, next: Option<Box<Node>>) -> Box<Node> {
Box::new(Node{value, next})
}
fn main() {
let mut list = cons(0, None);
for i in 1..10 {
list = cons(i, Some(list));
}
let mut curr = &mut *list;
if let Some(ref mut next) = curr.get_next_mut() {
// curr = next; // error: cannot assign to `curr` because it is borrowed
// println!("{}", curr.value);
}
println!("{}", curr.value);
if let Some(ref mut next) = {curr}.get_next_mut() {
curr = next;
println!("{}", curr.value);
}
// println!("{}", curr.value); // error: use of moved value: `curr.value`
}
This is unfortunate. Notionally, reassigning the variable shouldn’t invalidate the sub-borrow in this case: the sub-borrow is valid whether you chose to move or reborrow the variable. As far as I can tell, there would be no soundness problem with allowing a reborrowed mutable reference variable to be reassigned during the lifetime of the reborrow (though, this could be due to lack of imagination on my part ).
Questions: Would this be sound? Is this feasible to implement? Is this desirable?
With non-lexical lifetimes it should be possible to work around this limitation:
let mut curr = &mut *list;
{
let temp = curr;
curr = match temp.get_next_mut() {
Some(ref mut next) => next,
None => temp,
};
println!("{}", curr.value);
}
However, the code can get more convoluted as looping gets involved, forcing a somewhat unnatural style on the code:
let mut curr = &mut *list;
loop {
let temp = curr;
if let Some(ref mut next) = temp.get_next_mut() {
curr = next;
} else {
curr = temp;
break;
}
}
println!("{}", curr.value);
compared to:
let mut curr = &mut *list;
while let Some(ref mut next) = curr.get_next_mut() {
curr = next;
}
println!("{}", curr.value);