rust 为什么会发生“无法移出可变引用后面的'self.x'”错误?

3qpi33ja  于 2023-03-18  发布在  其他
关注(0)|答案(1)|浏览(177)

我正在试着用铁 rust 色写俄罗斯方块。我在这个项目中有一些结构体,我想把它们当作不可变的,即使它们会变异。
我用来实现这种行为的方法是这样的:

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

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

它允许你做这样的事情:

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

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

但当你做这样的事情时,他会冲你吼

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

让我困惑的是,为什么这个方法有效:

#[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: [['█', '░'], ['░', '░']] }
}

而这个却没有

#[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
这给出:

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 {
   |                     ^^^^

我做了一些研究,我觉得要么std::mem::swap,要么std::mem::take,要么std::mem::replace,
可以帮我,但我不知道怎么做。

8ulbf1ek

8ulbf1ek1#

你说得对,mem::[take,replace]()可以完成这项工作。
问题是,* 虽然你可以让变量暂时不初始化,但你不能让可变引用暂时不初始化(通过移出它),即使你在*之后重新分配它。
这种限制是有原因的:如果matrix.solidify()出现异常,我们将退出而不执行对matrix的循环赋值。稍后,我们可以从异常中恢复,并观察moved-from matrix
如果没有依赖项(和不安全代码),唯一的解决方案是即使在重新赋值时也留下一些东西,这样即使我们恐慌matrix也能保持初始化。如果Matrix实现Defaultstd::mem::take()可以帮助实现这一点--它保留默认值,而更通用的std::mem::replace()可以帮助实现这一点--它保留 * 一些 * 值:

#[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); 
    } 
}

或者:

#[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); 
    } 
}

如果这对你来说还不够好(例如,因为你没有一个好的默认值来插入,或者因为性能要求),你可以使用replace_with crate,它提供了replace_with::replace_with_or_abort(),在死机的情况下,它只会中止整个进程,阻止恢复的可能性:

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

请注意,您实际上可能需要interior mutability,而不是您现在正在做的事情。

相关问题