将Rust对象相互链接会导致借用问题

bvn4nwqk  于 2023-01-13  发布在  其他
关注(0)|答案(1)|浏览(102)

我有一组Rust struct示例,它们代表了游戏棋盘上的方格,每个方格都知道自己可以到达其他方格。
下面是我的例子:

enum Direction {
    North,
    South,
    East,
    West,
}

#[derive(Debug)]
struct Square {
    x: u16,
    y: u16,
}

impl Square {
    fn new(x: u16, y: u16) -> Self {
        Square { x, y }
    }
    fn spawn_another_square(&self, d: Direction) -> Self {
        let mut new_x = self.x;
        let mut new_y = self.y;
        match d {
            Direction::North => new_y -= 1,
            Direction::South => new_y += 1,
            Direction::East => new_x += 1,
            Direction::West => new_x -= 1,
        }
        Self::new(new_x, new_y)
    }
}

Square结构体在这个例子中只有xy坐标,没有真实的的数据。这个结构体实现了Debug,所以我们可以打印它,还有两个函数:new()spawn_another_square()spawn_another_square()返回一个新的正方形,使用这个正方形的坐标,但是根据需要向北、南、东或西移动。
如果我们添加一个main()来测试此器件,它可以正常工作。

fn main() {
    let northern_square = Square::new(10, 10);
    let central_square = northern_square.spawn_another_square(Direction::South);
    let western_square = central_square.spawn_another_square(Direction::West);
    let eastern_square = central_square.spawn_another_square(Direction::East);
    let southern_square = central_square.spawn_another_square(Direction::South);
    println!("Northern square: {:?}", northern_square);
    println!("Central square: {:?}", central_square);
    println!("Western square: {:?}", western_square);
    println!("Eastern square: {:?}", eastern_square);
    println!("Southern square: {:?}", southern_square);
}

main首先使用new()在任意坐标1010处创建northern_square,然后将spawn_another_square()“d转换为一个新的central_square,该新的central_square在第一个Direction::South的基础上移动了Direction::South,然后生成中心正方形以生成其他正方形,您明白了。
下一步是添加方块之间存在的路径信息。

#[derive(Debug)]
struct Square {
    x: u16,
    y: u16,
    to_the_north: Option<&Square>,
    to_the_south: Option<&Square>,
    to_the_east: Option<&Square>,
    to_the_west: Option<&Square>,
}
impl Square {
    fn new(x: u16, y: u16) -> Self {
        Square {
            x,
            y,
            to_the_north: None,
            to_the_south: None,
            to_the_east: None,
            to_the_west: None,
        }
    }
    fn spawn_another_square(&self, d: Direction) -> Self {
        let mut new_x = self.x;
        let mut new_y = self.y;
        match d {
            Direction::North => new_y -= 1,
            Direction::South => new_y += 1,
            Direction::East => new_x += 1,
            Direction::West => new_x -= 1,
        }
        // neighbour squares will be None on new square, even if set on this square
        Self::new(new_x, new_y)
    }
}

这里我们可以看到结构体的四个新元素,记录在四个方向上你会找到哪个正方形,新元素是Option<&Square>,因为在某些方向上可能根本没有正方形,所以Square示例可以在路径信息已知之前创建,而且,我们不希望正方形互相拥有。只是互相指向对方east和west不可能同时拥有central。另外,new()函数已经更新,可以为新元素提供None值。
当然,我的示例无法编译,因为expected named lifetime parameter,所以让我们修复它...

#[derive(Debug)]
struct Square<'a> {
    x: u16,
    y: u16,
    to_the_north: Option<&'a Square<'a>>,
    to_the_south: Option<&'a Square<'a>>,
    to_the_east: Option<&'a Square<'a>>,
    to_the_west: Option<&'a Square<'a>>,
}

impl<'a> Square<'a> {
    fn new(x: u16, y: u16) -> Self {
        Square {
            x,
            y,
            to_the_north: None,
            to_the_south: None,
            to_the_east: None,
            to_the_west: None,
        }
    }
    fn spawn_another_square(&self, d: Direction) -> Self {
        let mut new_x = self.x;
        let mut new_y = self.y;
        match d {
            Direction::North => new_y -= 1,
            Direction::South => new_y += 1,
            Direction::East => new_x += 1,
            Direction::West => new_x -= 1,
        }
        // neighbour squares will be None on new square, even if set on this square
        Self::new(new_x, new_y)
    }
}

它编译,但看起来像很多额外的<'a> s。
现在尝试设置一些路径,使用在Square结构体上实现的新函数和更新的main()

enum Direction {
    North,
    South,
    East,
    West,
}

#[derive(Debug)]
struct Square<'a> {
    x: u16,
    y: u16,
    to_the_north: Option<&'a Square<'a>>,
    to_the_south: Option<&'a Square<'a>>,
    to_the_east: Option<&'a Square<'a>>,
    to_the_west: Option<&'a Square<'a>>,
}

impl<'a> Square<'a> {
    fn new(x: u16, y: u16) -> Self {
        Square {
            x,
            y,
            to_the_north: None,
            to_the_south: None,
            to_the_east: None,
            to_the_west: None,
        }
    }
    fn spawn_another_square(&self, d: Direction) -> Self {
        let mut new_x = self.x;
        let mut new_y = self.y;
        match d {
            Direction::North => new_y -= 1,
            Direction::South => new_y += 1,
            Direction::East => new_x += 1,
            Direction::West => new_x -= 1,
        }
        Self::new(new_x, new_y) // neighbour squares will be None on new square, even if set on this square
    }
    fn set_a_path(&mut self, dir: Direction, dest: &'a Square) {
        match dir {
            Direction::North => self.to_the_north = Some(dest),
            Direction::South => self.to_the_south = Some(dest),
            Direction::East => self.to_the_east = Some(dest),
            Direction::West => self.to_the_west = Some(dest),
        };
    }
}

fn main() {
    let mut northern_square = Square::new(10, 10);
    let mut central_square = northern_square.spawn_another_square(Direction::South);
    let mut western_square = central_square.spawn_another_square(Direction::West);
    let mut eastern_square = central_square.spawn_another_square(Direction::East);
    let mut southern_square = central_square.spawn_another_square(Direction::South);

    northern_square.set_a_path(Direction::South, &central_square);

    println!("Northern square: {:?}", northern_square);
    println!("Central square: {:?}", central_square);
    println!("Western square: {:?}", western_square);
    println!("Eastern square: {:?}", eastern_square);
    println!("Southern square: {:?}", southern_square);
}

这是可行的,将正方形的所有变量都改为mut,然后用set_a_path()设置一条从northern_square向南到central_square的路径,运行正常。
但是添加额外的路径会引起我的问题。main中多一行就足够了。

central_square.set_a_path(Direction::East, &eastern_square);

这会导致错误。

error[E0502]: cannot borrow `central_square` as mutable because it is also borrowed as immutable
  --> src/main.rs:51:5
   |
49 |     northern_square.set_a_path(Direction::South, &central_square);
   |                                                  --------------- immutable borrow occurs here
50 |   //  central_square.set_a_path(Direction::North, &northern_square);
51 |     central_square.set_a_path(Direction::East, &eastern_square);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here
...
54 |     println!("Northern square: {:?}", northern_square);
   |                                       --------------- immutable borrow later used here

我是不是完全走错了路?我是不是在用一种“面向对象”的方式,而不是一种足够生 rust 的方式?或者我接近了,也许只是错过了奇怪的&refmut*组合?
square变量需要是可变的,以添加对其他方格的引用。我不认为我们可以创建路径已经编码的方格,因为第一个方格的邻居没有及时创建,它无法接收它的信息。

zzoitvuj

zzoitvuj1#

快速修复

对于Q最后出现的问题,修复方法非常简单,只需将需要修改的示例声明为mut(参见下面代码中的注解)。

更多信息

只要您不需要在多次共享后改变共享引用的示例,就可以在示例之间共享引用。或者,如果您克隆/复制示例而不是共享引用,也可以。

如果我们真的要实现一个游戏棋盘,我们可以使用array/Vec索引来避免square字段中的共享引用。如果我们有一个拥有数组的“棋盘”示例,或者拥有所有方格的Vec。像to_the_north这样的字段可以使用类型usize来代替引用。或者,可以使用某种内部基于数组位置 handle 对象。
此外,将板卡实现为类似数组的类型也是一种优化。通过处理器缓存数据的方式,连续数据的“局部性”可以使顺序访问非常快。这比将板卡实现为对堆分配示例的引用网要好,这些示例不一定是连续的。
如果类似上面的简单设计不起作用(带索引/句柄的数组),如果你确实想在这些square之间共享 MUTABLE 引用,那么你需要利用Rust的智能指针类/结构。使用智能指针的替代方案并不理想。Rust的使用模型通常期望在需要可变共享访问的地方使用智能指针类。
相关的智能指针类包括:

  • Rc-多个所有者;引用计数。
  • RefCell-单一所有者;多变的内部。

这些通常嵌套为Rc<RefCell<...>>。或者如果你只需要引用计数的多个所有者访问一个示例(没有内部可变性),你只需要Rc<...>RefCell<...>也可以独立使用,当它只有一个所有者时,封装的项需要根据需要修改,尽管拥有RefCell的对象本身是不可变的。
对于线程之间共享的类的示例,上面的类的线程安全类似物是Arc(与Rc相比)和Mutex(与RefCell相比)。

警告-循环引用

使用索引而不是引用作为句柄的Vec/array实现避免了以下问题。
如果square示例拥有彼此的Rc<...>引用,则可能会占用内存周期,而且周期中的引用计数可能永远不会递减到0,在这种情况下,如果您创建一个又一个电路板,即使它们超出了作用域,也可能会有一些延迟的堆分配引用计数对象随着时间的推移而累积起来。
如果您的应用程序只创建一个单板并在此期间使用它,这可能无关紧要。
处理这种情况的解决方案之一是使用弱指针。您可以阅读更多关于如何处理潜在内存周期here的内容

fn main()
{
    let northern_square = Square::new(10, 10);
    
    // Add 'mut'
    let mut central_square = northern_square.spawn_another_square(Direction::South);
    
    let western_square = central_square.spawn_another_square(Direction::West);
    let eastern_square = central_square.spawn_another_square(Direction::East);
    let southern_square = central_square.spawn_another_square(Direction::South);
    
    // Now this is okay.
    central_square.set_a_path(Direction::East, &eastern_square);
    
    println!("Northern square: {:?}", northern_square);
    println!("Central square: {:?}", central_square);
    println!("Western square: {:?}", western_square);
    println!("Eastern square: {:?}", eastern_square);
    println!("Southern square: {:?}", southern_square);    
}

相关问题