eliaxelang007
eliaxelang007

Reputation: 93

Why does this "cannot move out of `self.x` which is behind a mutable reference" error happen?

I'm trying to write Tetris in rust. I have structs in this project that I want to treat as immutable even though they do mutate.

The approach I'm using to achieve this kind of behavior is this:

#[derive(Debug)]
struct Example {
    foo: i8
}

impl Example {
    fn change(mut self) -> Self {
        self.foo = 8;
        self
    }
}

which allows you to do stuff like this:

let first = Example { foo: 0 };
let second = first.change();

println!("{:?}", second); // Example { foo: 8 }

but yells at you when you do things like this:

let first = Example { foo: 0 };
let second = first.change();
    
println!("{:?}", first); // error[E0382]: borrow of moved value: `first`

The part where I'm confused is, why does this work:

#[derive(Debug)]
struct Matrix {
    cells: [[char; 2]; 2]
}

impl Matrix {
    fn new() -> Self {
        Matrix {
            cells: [['░'; 2]; 2]
        }    
    }
    
    fn solidify(mut self, row: usize, column: usize) -> Self {
        self.cells[row][column] = '█';
        self
    }
}

fn main() {
    let matrix = Matrix::new();
    let matrix = matrix.solidify(0, 0);
    
    println!("{:?}", matrix); // Matrix { cells: [['█', '░'], ['░', '░']] }
}

when this doesn't?

#[derive(Debug)]
struct Matrix {
    cells: [[char; 2]; 2]
}

impl Matrix {
    fn new() -> Self {
        Matrix {
            cells: [['░'; 2]; 2]
        }    
    }
    
    fn solidify(mut self, row: usize, column: usize) -> Self {
        self.cells[row][column] = '█';
        self
    }
}

#[derive(Debug)]
struct Tetris {
    matrix: Matrix
}

impl Tetris {
    fn new() -> Self {
        Tetris {
            matrix: Matrix::new()
        }
    }
    
    fn change(&mut self) {
        self.matrix = self.matrix.solidify(0, 0); 
/*      -----------------------------------------
        This is where it yells at me ^                 */
    } 
}

fn main() {
    let mut tetris = Tetris::new();
    tetris.change();
    
    println!("{:?}", tetris); // error[E0507]: cannot move out of `self.matrix` which is behind a mutable reference
}

Playground

This gives:

error[E0507]: cannot move out of `self.matrix` which is behind a mutable reference
  --> src/main.rs:32:23
   |
32 |         self.matrix = self.matrix.solidify(0, 0); 
   |                       ^^^^^^^^^^^ -------------- `self.matrix` moved due to this method call
   |                       |
   |                       move occurs because `self.matrix` has type `Matrix`, which does not implement the `Copy` trait
   |
note: `Matrix::solidify` takes ownership of the receiver `self`, which moves `self.matrix`
  --> src/main.rs:13:21
   |
13 |     fn solidify(mut self, row: usize, column: usize) -> Self {
   |                     ^^^^

I've done some research and I feel like either std::mem::swap, std::mem::take, or std::mem::replace,

could do the trick for me, but I'm not exactly sure how.

Upvotes: 4

Views: 1712

Answers (2)

eliaxelang007
eliaxelang007

Reputation: 93

I've recently discovered that I don't really need the replace_with crate if I apply the same own mutable self and then return mutated self pattern to my change function.

All I had to do this whole time was change this:

impl Tetris {
    fn change(&mut self) {
        self.matrix = self.matrix.solidify(0, 0); 
/*      -----------------------------------------
        This is where it yells at me ^                 */
    }
}

to this:

impl Tetris {
    fn change(mut self) -> Self {
        self.matrix = self.matrix.solidify(0, 0);
/*      -----------------------------------------
        It doesn't yell at me here anymore ^   :D      */
        self
    }
}

and then use the new code like this:

fn main() {
    let tetris = Tetris::new();
    let tetris = tetris.change();
    
    println!("{:?}", tetris); // Tetris { matrix: Matrix { cells: [['█', '░'], ['░', '░']] } }
}

Upvotes: 1

Chayim Friedman
Chayim Friedman

Reputation: 71380

You're right. mem::[take,replace]() can do the work.

The problem is that while you can leave a variable uninitialized for a time, you cannot leave a mutable reference uninitialized for a time (by moving out of it), even if you reassign it after.

There is a reason to this limitation: panics. If matrix.solidify() panics, we will exit without executing the recurring assignment to matrix. Later, we could recover from the panic, and observe the moved-from matrix.

Without dependencies (and unsafe code), the only solution is to leave something behind even when we reassign, so that even if we panic matrix stays initialized. std::mem::take() can help with that if Matrix implements Default - it leaves the default value, while the more general std::mem::replace() can help otherwise - it leaves some value:

#[derive(Debug, Default)]
struct Matrix {
    cells: [[char; 2]; 2]
}

impl Tetris {
    fn change(&mut self) {
        let matrix = std::mem::take(&mut self.matrix);
        self.matrix = matrix.solidify(0, 0); 
    } 
}

Or:

#[derive(Debug)] // No `Default`.
struct Matrix {
    cells: [[char; 2]; 2]
}

impl Tetris {
    fn change(&mut self) {
        let matrix = std::mem::replace(&mut self.matrix, Matrix::new());
        self.matrix = matrix.solidify(0, 0); 
    } 
}

If this is not good enough for you (for example, because you don't have a good default value to insert, or because of performance requirements) you can use the replace_with crate. It provides replace_with::replace_with_or_abort(), that will just abort the whole process in case of a panic, preventing the possibility of recovering:

impl Tetris {
    fn change(&mut self) {
        replace_with::replace_with_or_abort(&mut self.matrix, |matrix| matrix.solidify(0, 0));
    }
}

Note that instead of what you're doing now, you may actually want interior mutability.

Upvotes: 3

Related Questions