Borrow Checker issue when returning from a while let loop

Hey, I'm very new to rust, attempting to create some ADTs to learn the ropes. I'm kinda stuck on trying to return the popped node from a linked list. As I understand, I'm mutably borrowing current.next in the loop so when I then attempt to take() it, the borrow checker flips out. I tried gpt, copilot and other sources about multiple mut borrows but can't seem to figure this out. Any tips on how to fix this would be greatly appreciated. Is this even a good approach in rust?

Minimal Reproduction:

pub struct Node {
    data: i32,
    next: Option<Box<Node>>,
}
pub struct LinkedList {
    head: Option<Box<Node>>,
}
impl LinkedList {
    fn pop(&mut self) -> Option<Box<Node>> {
        if self.head.is_none() {
            return None;
        }
        if self.head.as_mut().unwrap().next.is_none() {
            return self.head.take();
        }
        let mut current = self.head.as_mut().unwrap();
        while let Some(ref mut node) = current.next {
            if node.next.is_none() {
                return current.next.take();
            }
            current = node;
        }
        None
    }
}
fn setupList() -> LinkedList {
    let node = Box::new(Node {
        data: 1,
        next: Some(Box::new(Node {
            data: 2,
            next: Some(Box::new(Node {
                data: 3,
                next: Some(Box::new(Node {
                    data: 4,
                    next: Some(Box::new(Node {
                        data: 5,
                        next: None,
                    })),
                })),
            })),
        })),
    });
    let mut list = LinkedList { head: Some(node) };
    list.pop();
}

alternatively, I've also tried breaking out of the loop and returning outside, but the node borrow is still in scope apparently

while let Some(ref mut node) = current.next {
  if node.next.is_none() {
      break;
  }
  current = node;
}
current.next.take()

Error:

error[E0499]: cannot borrow `current.next` as mutable more than once at a time
--> src/adt/linked_list.rs:90:9
|
83 |         while let Some(ref mut node) = current.next {
|                        ------------ first mutable borrow occurs here
...
90 |         current.next.take()
|         ^^^^^^^^^^^^
|         |
|         second mutable borrow occurs here
|         first borrow later used here
For more information about this error, try `rustc --explain E0499`.

The problem is that in the while let Some(ref mut node) = current.next you're trying to both move and borrow current depending on some future condition:

  • if node.next.is_none() is true you want to borrow, as you will then return current.next.take()
  • otherwise you want to do current = node, which requires you to have node with the same lifetime as current, which in turn requires a move in the while let.

You can fix this by separating the two cases in a match, though that will require a loop instead of a while let. Something like this should work:

loop {
    match current.next {
        Some(ref node) if node.next.is_none() => return current.next.take(),
        Some(ref mut node) => current = node,
        None => break,
    }
}

Since the two Some are different branches one can move and the other can borrow.

4 Likes

FYI, linked lists (and other pointer-heavy types) are a notoriously bad way to learn Rust.

That said, I believe you're running into a limitation of the current borrow checker which is intended to be fixed by the next generation borrow checker (Polonius).


Which doesn't help you fix your code in the meanwhile. But here's an application of @SkiFire13's suggestion.

1 Like

Thanks! This works and I think I kinda get it. Much appreciated!

Somehow I'm not surprised that my approach to learning rust turns out to be notoriously bad :joy: . I've read about the borrow checker limitation and tried nightly but it still wouldn't budge. Thanks for the help and the resources, will definitely check them out :slight_smile:

-Z polonius, but it may not be complete yet so I can't really recommend using it beyond checking things.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.