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 mut matrix = Matrix::new();
    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
}

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.

>Solution :

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.

Leave a Reply