Rust 何时有必要避免生锈';谁是借书人?

Rust 何时有必要避免生锈';谁是借书人?,rust,Rust,我正在实施康威的人生游戏来教会自己。其思想是首先实现单线程版本,尽可能地优化它,然后对多线程版本执行相同的操作 我想实现一个替代的数据布局,我认为它可能对缓存更友好。其思想是将电路板上每个点的两个单元的状态存储在向量中,一个用于读取当前一代的状态,另一个用于写入下一代的状态,交替每个单元的访问模式 生成的计算(可在编译时确定) 基本数据结构如下: #[repr(u8)] pub enum CellStatus { DEAD, ALIVE, } /** 2 bytes */ pu

我正在实施康威的人生游戏来教会自己。其思想是首先实现单线程版本,尽可能地优化它,然后对多线程版本执行相同的操作

我想实现一个替代的数据布局,我认为它可能对缓存更友好。其思想是将电路板上每个点的两个单元的状态存储在向量中,一个用于读取当前一代的状态,另一个用于写入下一代的状态,交替每个单元的访问模式 生成的计算(可在编译时确定)

基本数据结构如下:

#[repr(u8)]
pub enum CellStatus {
    DEAD,
    ALIVE,
}

/** 2 bytes */
pub struct CellRW(CellStatus, CellStatus);

pub struct TupleBoard {
    width: usize,
    height: usize,
    cells: Vec<CellRW>,
}

/** used to keep track of current pos with iterator e.g. */
pub struct BoardPos {
    x_pos: usize,
    y_pos: usize,
    offset: usize,
}

pub struct BoardEvo {
    board: TupleBoard,
}
#[repr(u8)]
发布枚举单元状态{
死去的
活着的
}
/**2字节*/
发布结构CellRW(CellStatus,CellStatus);
pub结构元组板{
宽度:usize,
高度:usize,
细胞:Vec,
}
/**用于使用迭代器跟踪当前pos,例如*/
博德波斯酒店{
x_pos:usize,
y_pos:usize,
抵销:usize,
}
BoardEvo酒店{
董事会:元宝板,
}
给我带来麻烦的功能:

impl BoardEvo {
    fn evolve_step<T: RWSelector>(&mut self) {
        for (pos, cell) in self.board.iter_mut() {
            //pos: BoardPos, cell: &mut CellRW
            let read: &CellStatus = T::read(cell); //chooses the right tuple half for the current evolution step
            let write: &mut CellStatus = T::write(cell);

            let alive_count = pos.neighbours::<T>(&self.board).iter() //<- can't borrow self.board again!
                    .filter(|&&status| status == CellStatus::ALIVE)
                    .count();

            *write = CellStatus::evolve(*read, alive_count);
        }
    }
}

impl BoardPos {
    /* ... */
    pub fn neighbours<T: RWSelector>(&self, board: &BoardTuple) -> [CellStatus; 8] {
        /* ... */
    }
}
impl BoardEvo{
fn演进步骤(&mut self){
用于self.board.iter_mut()中的(位置、单元){
//位置:BoardPos、cell:&多个CellRW
let read:&CellStatus=T::read(cell);//为当前演化步骤选择正确的一半元组
let write:&mut CellStatus=T::write(cell);
let alive_count=pos.neights::(&self.board).iter()//
什么时候有必要绕过Rust的借货检查器

在下列情况下需要:

  • 借用检查器不够先进,无法确保您的使用安全
  • 您不希望(或不能)以不同的模式编写代码
具体来说,编译器无法判断这是安全的:

let mut array = [1, 2];
let a = &mut array[0];
let b = &mut array[1];
编译器不知道片的
IndexMut
实现在编译时做了什么(这是一个深思熟虑的设计选择)。据它所知,数组总是返回完全相同的引用,而不管索引参数是什么。我们可以断定此代码是安全的,但编译器不允许它

您可以用一种显然对编译器安全的方式重写此代码:

let mut array = [1, 2];
let (a, b) = array.split_at_mut(1);
let a = &mut a[0];
let b = &mut b[0];
如何做到这一点?为了确保它实际上是安全的:

fn在mut(&mut self,mid:usize)->(&mut[T],&mut[T]){
设len=self.len();
设ptr=self.as_mut_ptr();
不安全{

断言!(正在使用一个
UnsafeCell
-为什么你要一直使用
UnsafeCell
,而不是只使用一个
单元格
?谢谢你的深入解释。很有趣,我没有想过使用一个简单的索引循环。我现在不能测试,但这应该是解决我问题的一种方法。而且我只浏览了ch当我尝试std::mem::转换我的不可变引用时,编译器让我想到了UnsafeCell。实现了两个不同的版本,一个带有索引循环,另一个带有std::cell::cell使用内部可变性,从一些初步的基准测试来看,使用索引循环与我以前的实现相比,使用内部可变性的速度要快得多,速度要慢得多。我以前的实现使用了一个不安全的单元格来更改值,不太清楚为什么会这样,但这很有趣。@Oliver这对我来说实际上是非常反直觉的。
cell
在索引一个rray应该有一个(微小的)越界检查。你确实用
--release
cargo bench
运行了你的基准测试,对吗?是的,我做了。我也尽了最大努力,使不同的实现尽可能不存在差异,所以实现a)使用两个索引循环(x,y)和未选中的要访问每个单元格和实现,b)使用单元格和坐标感知迭代器。当我使用两个带常规get_mut()访问的索引循环时,结果更加接近,但循环仍然获胜。我不知道为什么会这样,但我假设一个版本可能允许更多的编译器优化。
fn split_at_mut(&mut self, mid: usize) -> (&mut [T], &mut [T]) {
    let len = self.len();
    let ptr = self.as_mut_ptr();

    unsafe {
        assert!(mid <= len);

        (from_raw_parts_mut(ptr, mid),
         from_raw_parts_mut(ptr.offset(mid as isize), len - mid))
    }
}