什么时候有必要规避 Rust 的借用检查器?

2024-03-16

我正在实施康威的生命游戏来自学 Rust。我们的想法是首先实现单线程版本,尽可能优化它,然后对多线程版本执行相同的操作。

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

基本数据结构如下:

#[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,
}

给我带来麻烦的功能:

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] {
        /* ... */
    }
}

特质RWSelector具有用于读取和写入单元元组的静态函数(CellRW)。它是针对两个零大小类型实现的L and R主要是避免为不同的访问模式编写不同的方法的一种方法。

The iter_mut()方法返回一个BoardIterstruct 是 cells 向量的可变切片迭代器的包装器,因此具有&mut CellRW as Item类型。也了解到目前的情况BoardPos(x 和 y 坐标,偏移量)。

我想我会迭代所有单元元组,跟踪坐标,计算每个(读取)单元的活动邻居的数量(我需要知道为此的坐标/偏移量),计算下一代的单元状态并写入元组各自的另一半。

当然,最后编译器向我展示了我的设计中的致命缺陷,正如我借用的self.board可变地在iter_mut()方法,然后尝试再次不可变地借用它以获取读取单元的所有邻居。

到目前为止我还没有能够想出一个好的解决方案来解决这个问题。我确实设法让它工作 引用不可变,然后使用UnsafeCell将对写入单元的不可变引用转变为可变引用。 然后,我通过以下方式写入对元组写入部分的名义上不可变的引用:UnsafeCell。 然而,这并不让我觉得是一个合理的设计,我怀疑在尝试并行化事物时可能会遇到问题。

有没有一种方法可以实现我在安全/惯用的 Rust 中提出的数据布局,或者这实际上是您必须使用技巧来规避 Rust 的别名/借用限制的情况?

另外,作为一个更广泛的问题,是否存在需要规避 Rust 借用限制的问题的可识别模式?


什么时候有必要规避 Rust 的借用检查器?

在以下情况下需要它:

  • 借用检查器不够先进,无法确保您的使用是否安全
  • 您不希望(或不能)以不同的模式编写代码

作为一个具体情况,编译器无法判断这是安全的:

let mut array = [1, 2];
let a = &mut array[0];
let b = &mut array[1];

编译器不知道执行了什么IndexMutfor a slice 在编译时执行此操作(这是经过深思熟虑的设计选择)。就其所知,无论索引参数如何,数组总是返回完全相同的引用。We可以看出这段代码是安全的,但编译器不允许它。

您可以以对编译器明显安全的方式重写它:

let mut array = [1, 2];
let (a, b) = array.split_at_mut(1);
let a = &mut a[0];
let b = &mut b[0];

这是怎么做到的?split_at_mut执行运行时检查 https://github.com/rust-lang/rust/blob/1.26.0/src/libcore/slice/mod.rs#L452-L462以确保它实际上is safe:

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))
    }
}

例如,借用检查器不是yet尽可能先进,请参阅什么是非词汇生命周期? https://stackoverflow.com/q/50251487/155423.

我借self.board可变地在iter_mut()方法,然后尝试再次不可变地借用它以获取读取单元的所有邻居。

如果您知道引用不重叠,那么您可以选择使用不安全代码来表达它。然而,这意味着you还选择承担维护 Rust 的所有不变量并避免未定义的行为 https://doc.rust-lang.org/reference/behavior-considered-undefined.html.

好消息是,这个沉重的负担是每个 C 和 C++ 程序员都必须承担的(或者至少是should)肩负着他们写的每一行代码。至少在 Rust 中,你可以让编译器处理 99% 的情况。

在许多情况下,有类似的工具Cell https://doc.rust-lang.org/std/cell/struct.Cell.html and RefCell https://doc.rust-lang.org/std/cell/struct.RefCell.html以允许内部突变。在其他情况下,您可以重写算法以利用作为Copy类型。在其他情况下,您可以在较短的时间内使用切片索引。在其他情况下,您可以采用多阶段算法。

如果您确实需要求助于unsafe代码,然后尽力将其隐藏在一个小区域并暴露安全的接口。

最重要的是,许多常见问题之前已经被问过(多次):

  • 如何在相同元素的另一个可变迭代中迭代可变元素? https://stackoverflow.com/q/46003212/155423
  • 改变嵌套循环内的项目 https://stackoverflow.com/q/29859892/155423
  • 如何在 Rust 中实现 HashMap 上带有突变的嵌套循环? https://stackoverflow.com/q/36281585/155423
  • Rust 修改嵌套循环中的结构的方法是什么? https://stackoverflow.com/q/29414171/155423
  • 嵌套迭代器的循环 https://stackoverflow.com/q/38779588/155423
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

什么时候有必要规避 Rust 的借用检查器? 的相关文章

随机推荐

  • 调用随机函数 Javascript,但不能调用同一函数两次

    我使用一个随机选择另一个有效函数的函数 但有时它会连续运行相同的函数两次甚至更频繁 有办法防止这种情况吗 我当前的代码 window setInterval function var arr func1 func2 func3 rand M
  • Node.js - 异步模块加载

    是否可以异步加载 Node js 模块 这是标准代码 var foo require foo js waiting for I O foo bar 但我想写这样的东西 require foo js function foo foo bar
  • 如何以编程方式获取 Google Cloud 定价详细信息?

    谁能告诉我如何以编程方式从 Google Cloud 网站获取 Google Cloud 定价详细信息 例如 Google Compute Engine Google Cloud Storage Google Cloud SQL 等的定价
  • Android 中的多屏幕 xml

    我正在开发2 2版本的android xml是根据这个版本设计的 模拟器规格 2 2版 内置 HVGA 内存 1024 现在我需要将此应用程序转换为4 0版本的三星galaxy s3 但屏幕非常拉伸 看起来不太好 如果有任何帮助 请提前致谢
  • Cloudinary - 上传预设必须位于未签名上传的白名单中

    我想将图像上传到 Cloudinary 使用 cordova 相机插件直接从 Ionic 中的相机拍摄 我收到代码 1 的错误 并显示消息 上传预设必须位于未签名上传的白名单中 如何解决这个错误 请帮忙 我编辑的js代码是 scope ca
  • 打印词性以及单词的同义词

    我有以下代码 用于从输入文本文件中获取单词并使用 WordNet 打印该单词的同义词 定义和例句 它根据词性将同义词与同义词集分开 即动词的同义词和形容词的同义词分别打印 例如 flabbergasted 一词的同义词有 1 flabber
  • Junit - Spring boot:测试时@Value始终为null

    有一个 Value注释的常量 在运行测试时没有被初始化 当构造函数中需要它时 它会抛出NullPointerException 要测试的示例类 class TestClass Value test value1 private String
  • laravel 中的 Auth::login($user) 无法登录用户

    我在用拉拉维尔 5 4 and 验证 登录 用户 显示类型错误 传递给 Illuminate Auth SessionGuard login 的参数 1 必须 实现接口 Illuminate Contracts Auth Authentic
  • 无需访问服务器或 phpMyADMIN 即可导出 SQL 表的简单方法

    我需要一种方法来轻松地将 MySQL 表中的数据从远程服务器导出然后导入到我的家庭服务器 我无法直接访问服务器 也没有安装 phpMyAdmin 等实用程序 不过 我确实有能力将 PHP 脚本放在服务器上 我如何获取数据 我问这个问题纯粹是
  • 什么是具有强度 1 边缘矩阵的设备互连 StreamExecutor

    我有四个 NVIDIA GTX 1080 显卡 当我初始化会话时 我看到以下控制台输出 Adding visible gpu devices 0 1 2 3 Device interconnect StreamExecutor with s
  • 函数的侦听器无法启动。 Azure.Storage.Blobs:服务请求失败

    我有一个包含普通函数和计时器函数的 Azure Function 项目 计时器功能突然停止工作并出现错误 The listener for function Function1 was unable to start Azure Stora
  • Eclipse 中有重新运行最近启动的程序的快捷方式吗?

    我使用 Eclipse 最常做的事情之一就是重新运行上一个程序 我这样做是为了运行 gt 运行历史记录 gt 最上面的项目 有没有快捷键可以做到这一点 I know of CTRL F11 but this does not work fo
  • Laravel 5.2 中应用于 API 路由的 Web 中间件

    我有以下路线 Route group prefix gt api v1 middleware gt api function Route resource authenticate AuthenticateController only g
  • 为什么DFS和BFS的时间复杂度都是O( V + E )

    BFS的基本算法 set start vertex to visited load it into queue while queue not empty for each edge incident to vertex if its no
  • 替换 Haskell 中的单个列表元素?

    我有一个元素列表 我希望更新它们 由此 Off Off Off Off 对此 Off Off On Off 由于我对 Haskell 有点陌生 所以我一直在使用 x xs y使用以下函数提取和更新各个组件 replace y z repla
  • 如何将具有多个“from”的 LINQ 表达式从查询语法转换为方法语法? [复制]

    这个问题在这里已经有答案了 我正在使用实体框架 我如何在 Lambda C 中编写以下 Linq 代码 var users from u in context Users ToList from e in u Events where e
  • FFMpeg 连续实时图像到视频编码

    我正在尝试使用 FFMpeg 获取图像文件流并将其转换为视频 现在 我已经成功地做到了这一点 但前提是我已经捕获了所有我想要的图像 我想做的是将图像保存到磁盘 实时录像机 时将其转换为视频 目前 当我在仍在抓取帧的情况下调用 FFMpeg
  • 对稀疏矩阵行求和的最快方法

    我有一个大的 csr matrix 1M 1K 我想添加行并获得一个具有相同列数但行数减少的新 csr matrix 其实我的问题和这个完全一样对 scipy sparse csr matrix 中的行求和 https stackoverf
  • WPF:无法设置焦点

    我希望从 PreviewTextInput 处理程序创建一个新控件并将焦点设置到它 但即使在新控件上调用 Focus 后 光标仍然位于旧文本框中 处理程序 UserControl PreviewTextInput 在包含此文本框的 User
  • 什么时候有必要规避 Rust 的借用检查器?

    我正在实施康威的生命游戏来自学 Rust 我们的想法是首先实现单线程版本 尽可能优化它 然后对多线程版本执行相同的操作 我想实现一种替代数据布局 我认为它可能对缓存更友好 这个想法是将板上每个点的两个单元的状态存储在向量中 一个单元用于读取