为什么会出现“无法移出可变引用后面的 `self.x`”错误?

问题描述 投票:0回答:2

我正在尝试用 Rust 编写俄罗斯方块。我在这个项目中有一些结构,我想将其视为 immutable,即使它们 do 变异。

我用来实现这种行为的方法是这样的:

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

游乐场

这给出:

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,

可以帮我解决这个问题,但我不确定怎么做。

rust move borrow-checker ownership ownership-semantics
2个回答
3
投票

你是对的。

mem::[take,replace]()
可以完成工作。

问题是虽然你可以让一个变量暂时未初始化,但你不能让一个可变引用暂时没有初始化(通过移出它),即使你在之后重新分配它。

这种限制是有原因的:恐慌。如果

matrix.solidify()
恐慌,我们将退出而不执行对
matrix
的重复分配。稍后,我们可以从恐慌中恢复过来,观察移动的
matrix
.

没有依赖项(和不安全的代码),唯一的解决方案是即使我们重新分配也留下一些东西,这样即使我们恐慌

matrix
保持初始化。
std::mem::take()
可以帮助解决这个问题,如果
Matrix
实现
Default
- 它保留默认值,而更一般的
std::mem::replace()
可以帮助否则 - 它留下 some 值:

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

请注意,您可能实际上想要内部可变性,而不是您现在正在做的事情。


0
投票

我最近发现我真的不需要 replace_with 箱子 如果我将相同的

own mutable self and then return mutated self
模式应用于我的
change
函数。

我一直要做的就是改变这个:

impl Tetris {
    /* ... */

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

    /* ... */
}

对此:

impl Tetris {
    /* ... */

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

    /* ... */
}

然后像这样使用新代码:

fn main() {
    let tetris = Tetris::new();
    let tetris = tetris.change();
    
    println!("{:?}", tetris); // Tetris { matrix: Matrix { cells: [['█', '░'], ['░', '░']] } }
}
© www.soinside.com 2019 - 2024. All rights reserved.