I have two implementation for removal function. Method 1 does not work because the compiler said curr
is borrowed.
What is the difference between while let Some(boxed_node) = curr
and match curr
?
fn delete_method_1(&mut self, val: i32) {
let mut curr = &mut self.head;
while let Some(boxed_node) = curr {
if boxed_node.val == val {
*curr = boxed_node.next.take(); // it does not work
} else {
curr = &mut boxed_node.next
}
}
}
while let Some(boxed_node) = curr {
| ---------- `*curr` is borrowed here
26 | if boxed_node.val == val {
27 | *curr = boxed_node.next.take(); // it does not work
| ^^^^^
| |
| `*curr` is assigned to here but it was already borrowed
| borrow later used here
fn delete_method_2(&mut self, val: i32) {
let mut curr = &mut self.head;
loop {
match curr {
None => return,
Some(boxed_node) if boxed_node.val == val => {
*curr = boxed_node.next.take();
},
Some(boxed_node) => {
curr = &mut boxed_node.next;
}
}
}
}
I expected Method 1 to work.
This is actually not really a difference between while let
and match
. If you wrote the second method to be exactly like the first it would look like this:
fn delete_method_2(&mut self, val: i32) {
let mut curr = &mut self.head;
loop {
match curr {
None => return,
Some(boxed_node) => {
if boxed_node.val == val {
*curr = boxed_node.next.take();
} else {
curr = &mut boxed_node.next;
}
}
}
}
}
And you'd get the exact same error.
What's actually happening is a little bit subtle. First of all, match ergonomics will dereference match scrutinees for you automatically. What's actuall happening is this:
fn delete_method_2(&mut self, val: i32) {
let mut curr = &mut self.head;
loop {
// v Rust inserts this dereference...
match *curr {
None => return,
// vvvvvvv and this borrow automatically
Some(ref mut boxed_node) => {
if boxed_node.val == val {
*curr = boxed_node.next.take();
} else {
curr = &mut boxed_node.next;
}
}
}
}
}
The while let
version looks pretty much the same. The problem here is that the line curr = &mut boxed_node.next
forces boxed_node
to be borrowed for the lifetime of curr
. With fake lifetime annotations:
fn delete_method_2(&mut self, val: i32) {
let mut curr = &'curr mut self.head;
loop {
match *curr {
None => return,
Some(ref 'curr mut boxed_node) => {
if boxed_node.val == val {
*curr = boxed_node.next.take();
} else {
// we force the compiler to match this lifetime
// vvvvv exactly
curr = &'curr mut boxed_node.next;
}
}
}
}
}
So because boxed_node
has to be borrowed for 'curr
, we're not allowed to assign to *curr
within that same scope. The borrow checker can't (yet) figure out that the two branches need to borrow boxed_node
for different lifetimes.
When you separate the cases into two distinct match arms, however, the compiler can figure out the appropriate lifetimes to borrow boxed_node
for in each case:
fn delete_method_2(&mut self, val: i32) {
let mut curr = &'curr mut self.head;
loop {
match *curr {
None => return,
Some(ref '_ mut boxed_node) if boxed_node.val == val => {
*curr = boxed_node.next.take();
}
Some(ref 'curr mut boxed_node) => {
curr = &'curr mut boxed_node.next;
}
}
}
}
In the first case, the compiler just needs to borrow boxed_node
for the duration of the take
call, after which curr
isn't considered borrowed anymore and we can re-assign it.
In the second case, the compiler can borrow boxed_node
for the 'curr
lifetime.
There are probably a myriad technical mistakes in that explanation, but big picture I think that's what's going on.