我正在用Rust编写一个扫雷器实现.目前,它由enum Cell组成,表示场的一个像元

#[derive(Copy, Clone, Debug)]
pub enum Cell {
    Mine,
    Flag,
    Empty(u8),
}

和代表字段本身的struct Feild

#[derive(Debug)]
pub struct Field(Vec<Vec<Cell>>);

因此,从根本上说,该场只是一个包含一个元素的元组:包含单元的矢量的矢量.

目前,Field struct 只有一个实现:

impl Field {
    pub fn new(width: u8, height: u8, number_of_mines: u16) -> Result<Self, ()> { ... }

    pub fn get_size(&self) -> (u8, u8, u16) { ... }

    fn insert_mines(mut self, number_of_mines: u16) -> Result<Self, ()> { ... }
}

通过调用,关联函数new允许创建Field的实例.get_size方法返回字段的维度.私有insert_mines方法将指定数量的地雷随机添加到田地中.

main()函数如下所示

fn main() {
    let field = field::Field::new(9, 9, 9).unwrap();

    println!("{}", field)
}

我正在创建一个9 x 9的字段,其中包含9个地雷,然后打印该字段(FieldCell都正确实现了Display个特征,但我不会在这里展示它,因为这不是我们的兴趣所在).

创建实例时,调用与new关联的函数:

pub fn new(width: u8, height: u8, number_of_mines: u16) -> Result<Self, ()> {
        let mut field = Field(vec![vec![Cell::Empty(0); width as usize]; height as usize]);

        field = field.insert_mines(number_of_mines)?;

        Ok(field)
    }

它随后调用insert_mines方法.Here it goes

fn insert_mines(mut self, number_of_mines: u16) -> Result<Self, ()> {
        // get the width of a single row and the total cells number of the field
        let (width, _, total_number_of_cells) = self.get_size();

        // return an error if the required number of mines is less than 1 or is more than the total number of cells
        // minus 1 (there should always be at least one cell without a mine)
        if number_of_mines < 1 || number_of_mines > (total_number_of_cells - 1) {
            return Err(());
        }

        // flatten the field for an easier interaction with it
        let mut flattened_field = self.0.into_iter().flatten().collect::<Vec<Cell>>();

        flattened_field.splice(
            0..number_of_mines as usize,
            (0..number_of_mines)
                .map(|_| Cell::Mine)
                .collect::<Vec<Cell>>(),
        );

        // shuffle the flattened field to randomly distribute the mines
        let mut rng = thread_rng();
        flattened_field.shuffle(&mut rng);

        // convert the flattened fields back into its original `Vec<Vec<u16>>` form
        self.0 = flattened_field
            .chunks(width as usize)
            .map(|chunk| chunk.to_vec())
            .collect::<Vec<Vec<Cell>>>();

        Ok(self)
    }

它的工作原理如下:

  1. Vec<Vec<Cell>>展平为Vec<Cell>(以便更容易使用)
  2. 将前n个元素替换为Cell::Mine
  3. 对向量进行混洗
  4. Vec<Cell>重建Vec<Vec<Cell>>并将结果设置为self.0(因此使用新字段更新原始字段)

一切都运行正常.当该程序运行时,它会打印如下内容

□ □ □ * □ □ □ □ □ 
□ □ □ □ □ □ □ □ □ 
□ □ □ * □ □ □ □ □ 
□ □ □ □ * □ □ * □ 
* □ □ * □ □ □ □ □ 
□ □ □ □ * □ □ □ * 
□ □ □ □ □ □ □ □ □ 
□ * □ □ □ □ □ □ □ 
□ □ □ □ □ □ □ □ □ 

这是完全正确的(正方形是空单元格,*是地雷).

However,我不喜欢insert_mines法取得油田所有权的方式.事实上,我甚至不确定这是不是很糟糕.如果可以,那么我想知道为什么在这种特定情况下它是可以的(虽然通常建议给参数尽可能少的"访问能力").如果它确实不好,最好改写成使用&mut self,那么我想知道如何做到这一点,因为我自己的try 会遇到问题.


关于my attempts:

我首先要更新INSERT_MINES方法的签名

fn insert_mines(mut self, number_of_mines: u16) -> Result<Self, ()> { ... }变成fn insert_mines(&mut self, number_of_mines: u16) -> Result<(), ()> { ... }.

然后在new a.f.出现以下错误:

field = field.insert_mines(number_of_mines)?;

// Type mismatch [E0308] expected `Field`, but found `()` 
`?` operator has incompatible types [E0308] expected `Field`, found `()` Note: `?` operator cannot convert from `()` to `field::Field`

预期中.该方法应该就地更新该字段,因此不再需要分配任何内容.go 掉field = 的部分.

然后,insert_mines本身出现了一个错误:

// flatten the field for an easier interaction with it
let mut flattened_field = self.0.into_iter().flatten().collect::<Vec<Cell>>();

// cannot move out of `self` which is behind a mutable reference [E0507] move occurs because `self.0` has type `std::vec::Vec<std::vec::Vec<field::cell::Cell>>`, which does not implement the `Copy` trait Note: `std::iter::IntoIterator::into_iter` takes ownership of the receiver `self`, which moves `self.0` Help: you can `clone` the value and consume it, but this might not be your desired behavior

继续解决问题,这就是它归结为:

fn insert_mines(&mut self, number_of_mines: u16) -> Result<(), ()> {
        // get the width of a single row and the total cells number of the field
        let (width, _, total_number_of_cells) = self.get_size();

        // return an error if the required number of mines is less than 1 or is more than the total number of cells
        // minus 1 (there should always be at least one cell without a mine)
        if number_of_mines < 1 || number_of_mines > (total_number_of_cells - 1) {
            return Err(());
        }

        // flatten the field for an easier interaction with it
        let mut flattened_field = self.0.iter_mut().flatten().collect::<Vec<&mut Cell>>();

        flattened_field.splice(
            0..number_of_mines as usize,
            (0..number_of_mines)
                .into_iter()
                .map(|_| &mut Cell::Mine)
                .collect::<Vec<&mut Cell>>(),
        );

        // shuffle the flattened field to randomly distribute the mines
        let mut rng = thread_rng();
        flattened_field.shuffle(&mut rng);

        // convert the flattened fields back into its original `Vec<Vec<u16>>` form
        self.0 = flattened_field
            .chunks(width as usize)
            .map(|chunk| chunk.to_vec())
            .collect::<Vec<Vec<&mut Cell>>>();

        Ok(())
    }

误差现在在self.0 = ...的直线上,并表示

mismatched types [E0308] expected `Vec<Vec<Cell>>`, found `Vec<Vec<&mut Cell>>` Note: expected struct `std::vec::Vec<std::vec::Vec<field::cell::Cell>>` found struct `std::vec::Vec<std::vec::Vec<&mut field::cell::Cell>>`
Type mismatch [E0308] expected `Vec<Vec<Cell>>`, but found `Vec<Vec<&mut Cell>>`

据我所知,这意味着Field(Vec<Vec<Cell>>)需要一个Cell的向量,但我试图给它赋值的是一个指向Cell的可变引用的向量.

所以,我在想,也许我采取了一种错误的方法.或者,方法可能是正确的,但我在实现中遗漏了一些东西(我的 idea 是以某种方式将<Vec<Vec<&mut Cell>>转换回<Vec<Vec<Cell>>,但不知道确切的how).总的来说,我在寻找关于我的问题的指导和关于我目前方法的反馈.

推荐答案

一般的解决方案实际上可能比您想象的更接近取得所有权的版本.Rust有一个名为std::mem::swap的内置函数,允许您获得可变借入值的所有权.

下面是您可以在代码中使用它的地方.

fn insert_mines(&mut self, number_of_mines: u16) -> Result<(), ()> {
    let mut temp_self = Field(Vec::new());
    std::mem::swap(self, &mut temp_self);

在做任何事情之前,你把借来的self换成自己的temp_self.对于函数的其余部分,self将包含一个空网格,您将使用temp_self而不是self.例如:

    // flatten the field for an easier interaction with it
    let mut flattened_field = temp_self.0.into_iter().flatten().collect::<Vec<Cell>>();

最后,您可以再次使用swap将实数字段返回到self.

    std::mem::swap(self, &mut temp_self);
    Ok(())
}

更具体地说,由于Vec实现了Default,因此可以使用std::mem::take来以更少的代码获得值.这只交换了Field中的数据,所以你在self.0而不是self上调用它.在函数的顶部:

let temp_self = Field(std::mem::take(&mut self.0));

另外,因为你实际上不需要self中的占位符Vec,所以你可以用赋值来覆盖它.

*self = temp_self;
Ok(())

所有前面的 idea 通常适用于可以快速构造的类型(这是大多数类型),但对于您的特定情况,您实际上不需要所有权来执行任何您正在做的事情.有很多方法可以做到这一点,但其中一种方法是收集你所有的Cell条推荐信,对它们进行洗牌,然后将第一条推荐信设置为地雷.

fn insert_mines(&mut self, number_of_mines: u16) -> Result<(), ()> {
    // get the width of a single row and the total cells number of the field
    let (_, _, total_number_of_cells) = self.get_size();

    // return an error if the required number of mines is less than 1 or is more than the total number of cells
    // minus 1 (there should always be at least one cell without a mine)
    if number_of_mines < 1 || number_of_mines > (total_number_of_cells - 1) {
        return Err(());
    }

    // flatten the field for an easier interaction with it
    let mut flattened_field = self.0.iter_mut().flatten().collect::<Vec<&mut Cell>>();

    // shuffle the flattened field to randomly distribute the mines
    let mut rng = thread_rng();
    flattened_field.shuffle(&mut rng);

    for cell in flattened_field.into_iter().take(number_of_mines as usize) {
        *cell = Cell::Mine;
    }

    Ok(())
}

你可以做更多的事情来让这句话更地道,表现得更好,但这是为了另一篇文章.

Rust相关问答推荐

为什么幻影数据不能自动推断?

铁 rust ,我的模块介绍突然遇到了一个问题

无符号整数的Rust带符号差

JSON5中的变量类型(serde)

如何防止Cargo 单据和Cargo 出口发布( crate )项目

在macro_rule中拆分模块和函数名

考虑到Rust不允许多个可变引用,类似PyTorch的自动区分如何在Rust中工作?

如何使用 Bincode 在 Rust 中序列化 Enum,同时保留 Enum 判别式而不是索引?

我可以禁用发布模式的开发依赖功能吗?

Rust 中的生命周期:borrow 的 mut 数据

如何在 Rust 中将 Vec> 转换为 Vec>?

从 Axum IntoResponse 获取请求标头

为什么 for_each 在释放模式(cargo run -r)下比 for 循环快得多?

当 T 不是副本时,为什么取消引用 Box 不会抱怨移出共享引用?

Abortable:悬而未决的期货?

在 Rust 中获得准确时间的正确方法?

为什么我可以从读取的可变自引用中移出?

如何用另一个变量向量置换 rust simd 向量?

为什么我不能为 Display+Debug 的泛型类型实现 std::error::Error 但有一个不是泛型参数的类型?

为移动和借位的所有组合实现 Add、Sub、Mul、Div