在二维网格上实现可变枚举方法



我在rust:中有一个2D网格的主要工作实现

use itertools::Itertools;
#[derive(Debug)]
pub struct Grid<T> {
width: usize,
height: usize,
items: Vec<T>,
}
impl<T> Grid<T> {
pub fn new(width: usize, height: usize, initializer: impl Fn() -> T) -> Self {
Self {
width,
height,
items: (0..height * width).map(|_| initializer()).collect(),
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
pub fn size(&self) -> usize {
self.width * self.height
}
pub fn get(&self, x: usize, y: usize) -> Result<&T, &str> {
if self.on_grid(x as isize, y as isize) {
Ok(&self.items[self.coordinate_to_index(x, y)])
} else {
Err("coordinate not on grid")
}
}
pub fn get_mut(&mut self, x: usize, y: usize) -> Result<&mut T, &str> {
if self.on_grid(x as isize, y as isize) {
let index = self.coordinate_to_index(x, y);
Ok(&mut self.items[index])
} else {
Err("coordinate not on grid")
}
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.items.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.items.iter_mut()
}
pub fn enumerate(&self) -> impl Iterator<Item = (usize, usize, &T)> {
self.items.iter().enumerate().map(|(index, item)| {
let (x, y) = self.index_to_coordinate(index);
(x, y, item)
})
}
pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (usize, usize, &mut T)> {
self.items.iter_mut().enumerate().map(move |(index, item)| {
let (x, y) = self.index_to_coordinate(index);
(x, y, item)
})
}
pub fn neighbors(&self, x: usize, y: usize) -> impl Iterator<Item = (usize, usize, &T)> {
self.neighbor_indices(x, y)
.map(|(x, y)| (x, y, &self.items[self.coordinate_to_index(x, y)]))
}
fn coordinate_to_index(&self, x: usize, y: usize) -> usize {
y * self.width + x
}
fn index_to_coordinate(&self, index: usize) -> (usize, usize) {
let x = index % self.width;
let y = (index - x) / self.width;
(x, y)
}
fn neighbor_indices(&self, x: usize, y: usize) -> impl Iterator<Item = (usize, usize)> + '_ {
neighbor_offsets(2)
.map(move |item| (x as isize + item[0], y as isize + item[1]))
.filter(|&(x, y)| self.on_grid(x, y))
.map(|(dx, dy)| (dx as usize, dy as usize))
}
fn on_grid(&self, x: isize, y: isize) -> bool {
0 <= x && x < self.width as isize && 0 <= y && y < self.height as isize
}
}
fn neighbor_offsets(dimension: usize) -> impl Iterator<Item = Vec<isize>> {
(-1..1)
.map(|index| index as isize)
.combinations_with_replacement(dimension)
// skip zero offset
.filter(|items| !items.iter().all(|&item| item == 0))
}

Cargo.toml

[package]
name = "grid"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
itertools = "*"

然而,对于enumerate_mut(),的编译当前失败

$ cargo build
Compiling either v1.8.0
Compiling itertools v0.10.5
Compiling grid v0.1.0 (/home/neumann/Projekte/grid)
error[E0505]: cannot move out of `self` because it is borrowed
--> src/lib.rs:64:47
|
63 |       pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (usize, usize, &mut T)> {
|                            - let's call the lifetime of this reference `'1`
64 |           self.items.iter_mut().enumerate().map(move |(index, item)| {
|           ---------------------                 ^^^^^^^^^^^^^^^^^^^^ move out of `self` occurs here
|           |
|  _________borrow of `self.items` occurs here
| |
65 | |             let (x, y) = self.index_to_coordinate(index);
| |                          ---- move occurs due to use in closure
66 | |             (x, y, item)
67 | |         })
| |__________- returning this value requires that `self.items` is borrowed for `'1`
For more information about this error, try `rustc --explain E0505`.
error: could not compile `grid` due to previous error
warning: build failed, waiting for other jobs to finish...

我知道我可以将方法index_to_coordinate()写成一个纯函数,但我想要一个类似于enumerate()使用成员函数的解决方案。这能做到吗?如果是,如何?

我想要一个解决方案,它使用类似于enumerate((的成员函数。这能做到吗?如果是,如何?

无法执行。不能借用self,而self.items是可变借用的。句点考虑在没有成员函数的情况下工作的公式:

pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (usize, usize, &mut T)> {
self.items.iter_mut().enumerate().map(|(index, item)| {
let x = index % self.width;
let y = (index - x) / self.width;
(x, y, item)
})
}

唯一可行的方法是因为不相交的借用,即编译器知道.width.height是与.items完全分离的对象(实际上这只适用于2021版,因为不相交借用发生在闭包内外(。但是,如果您使用self的成员,编译器就失去了这一知识,必须考虑self可以访问该成员函数中的items,这是不允许的,因为前面的可变借位必须保持独占。在enumerate()方法中使用成员函数是有效的,因为可以共享不可变的借用。

函数签名是借用检查、类型检查和类型推理构建的契约。如果成员函数可以访问self,则必须假设它将访问self所有。跨函数接口的部分借用没有语法。


根本做不到?即使我使用unsafe

即使使用了unsafe,也不允许违反Rust的借用规则,因此不能在.items被可变借用时使用&Self可以访问.width.height,但必须通过指针:*const Self来完成。以下是它的样子(在操场上与Miri一起测试(:

pub fn enumerate_mut(&mut self) -> impl Iterator<Item = (usize, usize, &mut T)> {
let this = self as *const Self;
self.items.iter_mut().enumerate().map(move |(index, item)| {
let (x, y) = unsafe { Self::index_to_coordinate(this, index) };
(x, y, item)
})
}
unsafe fn index_to_coordinate(this: *const Self, index: usize) -> (usize, usize) {
let width = *std::ptr::addr_of!((*this).width);
let height = *std::ptr::addr_of!((*this).height);
let x = index % width;
let y = (index - x) / width;
(x, y)
}

然而,我希望我们能同意,这将不再是成员函数,因为它缺少self.index_to_coordinate语法。当然,我根本不主张你使用这个,这只是一个证明,即使走向荒谬也不可能。

只需使用免费函数。:(

最新更新