Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable borrow tied to mutable borrow causes "cannot borrow `*self` as mutable more than once at a time" [duplicate]

I am learning Rust by exercise. In this file the goal is to update cells as in a spreadsheet: when a value changes, all cells that are derived from it have to be recalculated. Here, those are called that cell's parents.

Updating a cell value proves no problem, but updating the parents gets me fighting the borrow checker. When I have retrieved the cell from a HashMap and updated the value, I no longer need a mutable reference - so I tried wrapping it in an immutable reference instead. That way I only have to find it once.

But it seems Rust figures since I originally got my immutable reference from a borrowed &mut self, it must still be tied to it. This obviously prevents me from reusing self a second time.

use std::collections::HashMap;
use std::vec::Vec;

struct Cell {
    value: i32,
    parents: Vec<u32>,
}

pub struct Sheet {
    table: HashMap<u32, Cell>,
}

impl Sheet {
    pub fn set_value(&mut self, identifier: u32, new_value: i32) {
        let mut updated_cell: Option<&Cell> = None;
        if let Some(cell) = self.table.get_mut(&identifier) {
            let Cell { value, .. } = cell;
            *value = new_value;
            updated_cell = Some(cell);
        }
        if let Some(cell) = updated_cell {
            recalculate(self, &cell.parents);
        }
    }
}

fn recalculate(_sheet: &mut Sheet, _cells: &[u32]) {}
error[E0499]: cannot borrow `*self` as mutable more than once at a time
  --> src/lib.rs:20:16
   |
16 |         if let Some(cell) = self.table.get_mut(&identifier) {
   |                             ---------- first mutable borrow occurs here
...
22 |             recalculate(self, &cell.parents);
   |                         ^^^^  ------------- first borrow later used here
   |                         |
   |                         second mutable borrow occurs here

I want to know if there is a solution which avoids searching a second time or taking an unnecessary vector copy. I have tried to adjust the code many times, but not all of the syntax is yet clear to me yet.

like image 710
Arne J Avatar asked May 21 '19 06:05

Arne J


1 Answers

Rust is protecting you from a potentially dangerous situation. There is nothing in recalculate's signature to guarantee that it won't mutate sheet in such at way that the references in cells become invalid. For example recalculate could delete some cells and then the references in cell.parents would be dangling pointers.

You probably need to pass a clone of the parent cells instead:

if let Some(cell) = updated_cell {
    let parents = cell.parents.clone();
    recalculate(self, &parents);
}

Alternatively, you may need to consider a different data model, which separates the mutability of individual cells from the mutability of the overall structure. For example, you could wrap cells in a std::cell::Cell or std::cell::RefCell, and pass an immutable reference to the Sheet.

like image 140
Peter Hall Avatar answered Nov 06 '22 15:11

Peter Hall