Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

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:

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

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.

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading