Why is `tail` still borrowed after the `while let Some() = xxx` loop?

Viewed 113

I've been solving some leetcode problems where I hit an issue I could not explain.

Here is the full source code:

#[derive(Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }
}

fn main() {
    let mut list = Some(Box::new(ListNode {
        val: 1,
        next: Some(Box::new(ListNode {
            val: 2,
            next: Some(Box::new(ListNode { val: 3, next: None })),
        })),
    }));

    let mut tail = &mut list.as_mut().unwrap().next;

    // This compiles fine
    // while tail.is_some() {
    //     tail = &mut tail.as_mut().unwrap().next;
    // }

    // This compiles fine
    // while let Some(ref mut node) = tail {
    //     tail = &mut node.next;
    // }

    // However this does not
    while let Some(node) = tail.as_mut() {
        tail = &mut node.next;
    }

    *tail = Some(Box::new(ListNode::new(4)));
    println!("{:#?}", list);
}

And this is the error:

error[E0506]: cannot assign to `*tail` because it is borrowed
  --> src/main.rs:39:5
   |
35 |     while let Some(node) = tail.as_mut() {
   |                            ---- borrow of `*tail` occurs here
...
39 |     *tail = Some(Box::new(ListNode::new(4)));
   |     ^^^^^
   |     |
   |     assignment to borrowed `*tail` occurs here
   |     borrow later used here

Why is tail still borrowed after the while let Some() = xxx loop ?

1 Answers

This is due to the interaction of while let ... and the scrutinee tail.as_mut() in that while let, which is a value expression in a place expression context.

Simply put, in order for the body of the while let loop to be able to access the binding (node), the compiler needs to borrow tail (created before the loop), call as_mut() on the Option, keep the implicit temporary borrow on that Option alive(!), and bind the value as_mut() returns to node; as far as the compiler knows, the lifetime of node depends on that Option, because it is funneled through &'a mut tail -> as_mut(&'a mut self) -> &'a mut ListNode.

When you then reassign tail = &mut node.next, this is still a value of the implicit lifetime 'a, borrowed on the original temporary which the loop created.

When you then assign to *tail after the loop, a conflict occurs: There is an implicit borrow on the original Option, which is used to call as_mut() to first create node and then tail, but now you are assigning to - as far as the compiler can see - that Option. But you can't invalidate that Option while it is borrowed, so an error occurs.

This is why the compiler points to borrow of *tail occurs here (notice the dereference) in the scrutinee-position of the while let-loop and then - confusingly - points to *tail = Some(...) being the point where the borrow is used.

The other two examples compile fine because there is no temporary created in the scrutinee to do the method call on Option.

Related