Managing Mutable References and Lifetimes in Rust Linked List Deletion

// Definition for singly-linked list.
// pub struct ListNode { pub val: i32, pub next: Option<Box<ListNode>> }

impl Solution {
    pub fn remove_elements(root: Option<Box<ListNode>>, target: i32) -> Option<Box<ListNode>> {
        let mut root = root;
        while let Some(n) = root.as_ref() {
            if n.val == target {
                root = root.unwrap().next;
            } else {
                break;
            }
        }

        let mut ptr = &mut root;

        while let Some(n) = ptr {
            while let Some(sub) = n.next.as_mut() {
                if sub.val == target {
                    n.next = sub.next.take();
                } else {
                    break;
                }
            }
            ptr = &mut n.next;
        }

        root
    }
}

The statement let mut root = root; employs variable shadowing to bind the ownership of the immutable root parameter to a mutable variable, enabling in-place modifications during linked list traversal.

In the initial loop, n holds an immutable reference to root. The subsequent line root = root.unwrap().next modifies root while n is seemingly still active. This is permitted due to Non-Lexical Lifetimes (NLL). The borrow tied to n expires after its final usage in the condition n.val == target, clearing the way for root to be consumed and reassigned.

The declaration let mut ptr = &mut root; establishes a mutable tracker. The variable ptr is mutable, its type is a mutable reference &mut Option<Box<ListNode>>, and it borrows root mutably.

Within while let Some(n) = ptr, an implicit reborrow occurs. n becomes a mutable reborrow of the Box<ListNode> inside the Option. While a reborrow is active, the original borrowed reference (ptr) cannot be accessed, preventing overlapping mutable aliases.

The inner loop while let Some(sub) = n.next.as_mut() takes a mutable reference to the next field of n. Although sub and n.next both refer to the same underlying memory location, Rust permits borrowing distinct structural fields, and sub acts as a reborrow of the pointer held in n.next.

The assignment n.next = sub.next.take() might appear to use sub and mutate n.next simultaneously. However, Rust evaluates the right-hand side of an assignment before the left-hand side. The borrow on sub concludes once sub.next.take() executes, well before the mutation of n.next occurs.

Finally, ptr = &mut n.next; updates the mutable reference. The reborrow associated with n terminates at this point in the loop iteration, freeing ptr to be reassigned to a new mutable reference targeting the subsequent node.

Tags: rust linkedlist BorrowChecker NLL Reborrowing

Posted on Tue, 23 Jun 2026 16:54:32 +0000 by speckledapple