How do I pass mutable self that was already borrowed?

Code:

struct MoveAction {
    dest_x: i32,
    dest_y: i32,
}

impl MoveAction {
    fn update(&self, node: &mut Node) {
        node.x = self.dest_x;
        node.y = self.dest_y;
    }
}

struct Node {
    x: i32,
    y: i32,
    actions: Vec<MoveAction>,
}

impl Node {
    fn update(&mut self) {
        for action in self.actions.iter() {
            action.update(self);
        }
    }

    fn add_action(&mut self, action: MoveAction) {
        self.actions.push(action);
    }
}

fn main() {}

Error:

error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
  --> src\main.rs:22:13
   |
21 |         for action in self.actions.iter() {
   |                       -------------------
   |                       |
   |                       immutable borrow occurs here
   |                       immutable borrow later used here
22 |             action.update(self);
   |             ^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

It feels like 'self' is borrowed by for loop, then how can I borrow it once more to pass mutable 'self' into action.update(action: &Node)?

You can't have a mutable borrow and an immutable borrow of the same value at the same time. That's the basic rule of Rust borrow checking.

The fundamental issue in this code is that you're trying to create a data structure where the objects being stored get references to the thing storing them. That's inherently circular; there's just no way to do that with regular references alone.

We'd need to know more about your real use case to tell what you should be doing instead, but you may be interested in docs like Learn Rust With Entirely Too Many Linked Lists, the standard collections, or crates like intrusive_collections. But the really short version is that to make code like this actually compile, you either need to pay the runtime cost of reference counting, or you need to drop to unsafe code (ideally via a dedicated crate), or (far more likely) you need to rethink your architecture so that this pattern never arises in the first place.

If you are not bound (for some reason) to structure the code the way it is, we can move the update logic within Node#update to work around the borrow checker and eliminate impl MoveAction.

The update() in your impl Node can then look like:

impl Node {
    fn update(&mut self) {
        for action in self.actions.iter() {
            self.x = action.dest_x;
            self.y = action.dest_y;
        }
    }
}

I wanted to have Action trait with MoveAction struct implementing Action. With Node having Vec<Box<dyn Action>> (simulating OOP vector of interfaces) field and add_action(action: impl Action) method. So that when doing node.add_action(action) I could pass any object that implements Action trait, and then in node.update() iterate over actions and call update() for each of them

The Borrow Checker Is Helping You Here

If I've parsed this correctly, then structurally you are trying to have each Node keep a list of actions, which you can periodically tell it to apply to itself. This runs into a direct problem which the borrow checker is explicitly designed to prevent: what if your actions change the Node, including the Vector of actions you are iterating over?

In C++ and other unprotected languages, you would be quite free to accidentally clear the Vector during one of your actions (hey, you have a mutable reference to the thing owning the vector! Who's to say you can't clear it?!) and then the very next loop iteration would walk into perhaps rewritten memory, or stale data, etc.

So, if you're going to iterate over the actions and apply stuff to a Node, then you can't do it this way. You need to either write code which processes the actions on a new x/y pair and then stores the result after the iteration, or move the actions to a different object etc etc.

Although technically your code could work correctly, the compiler is unable to reason about whether that is true; the principles by which safe code works have been violated in your design, so you have to change it. If you can refactor it to separate the thing you're iterating over from the thing you are changing, then you will have a better design anyway.

3 Likes

thanks for clarifying :smiley:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.