我想像下面那样实现自定义迭代器,但是不能解决引用问题。
use itertools::Product;
use std::ops::Range;
struct Iter2DMut<'a, T: 'a> {
data: &'a mut [T],
shape: (usize, usize),
idx_iter: Product<Range<usize>, Range<usize>>,
}
impl<'a, T: 'a> Iterator for Iter2DMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
if let Some((i, j)) = self.idx_iter.next() {
Some(&mut self.data[i + self.shape.0 * j])
} else {
None
}
}
}
并获得以下错误消息。
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
--> src/main.rs:13:23
|
13 | Some(&mut self.data[i + self.shape.0 * j])
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
[基于作者在评论中的澄清,我假设此处的目标是迭代矩阵的矩形子矩阵。例如,给定矩阵
100 200 300 400 500 600
110 210 310 410 510 610
120 220 320 420 520 620
130 230 330 430 530 630
以行为主的切片表示
[100, 200, 300, 400, 500, 600, 110, ..., 530, 630]
我们想遍历子矩阵,例如
210 310 410 510
220 320 420 520
再次按行优先顺序排列,所以我们将得到的元素按顺序排列
210, 310, 410, 510, 220, 320, 420, 520
在这种情况下,可以使用安全Rust相对有效地解决此问题。技巧是在split_at_mut的data
字段中使用切片的Iter2DMut
方法,以便根据需要一次剥离一个可变参考。随着迭代的进行,data
字段将更新为越来越小的片,以使其不再包含已被迭代的元素;这是必要的,因为在任何给定的迭代中,Rust都不允许我们生成对元素的可变引用,同时还保留包含该元素的可变切片。通过更新切片,我们可以确保它始终与先前所有对next()
的调用所生成的可变引用不相交,从而满足Rust借阅检查器的要求。这是可以做到的:
use itertools::{Itertools, Product};
use std::ops::Range;
use std::mem;
struct Iter2DMut<'a, T: 'a> {
data: &'a mut [T],
full_shape: (usize, usize),
sub_shape: (usize, usize),
idx_iter: Product<Range<usize>, Range<usize>>,
}
impl<'a, T> Iter2DMut<'a, T> {
fn new(
data: &'a mut [T],
full_shape: (usize, usize),
sub_shape: (usize, usize),
offset: (usize, usize),
) -> Self {
assert!(full_shape.0 * full_shape.1 == data.len());
assert!(offset.0 + sub_shape.0 <= full_shape.0);
assert!(offset.1 + sub_shape.1 <= full_shape.1);
Iter2DMut {
data: &mut data[offset.0 * full_shape.1 + offset.1 ..],
full_shape,
sub_shape,
idx_iter: (0..sub_shape.0).cartesian_product(0..sub_shape.1)
}
}
}
impl<'a, T: 'a> Iterator for Iter2DMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
if let Some((_, j)) = self.idx_iter.next() {
let mut data: &'a mut [T] = &mut [];
mem::swap(&mut self.data, &mut data);
let (first, rest) = data.split_at_mut(1);
data = rest;
if j == self.sub_shape.1 - 1 {
let n_skip = self.full_shape.1 - self.sub_shape.1;
let (_, rest) = data.split_at_mut(n_skip);
data = rest;
}
self.data = data;
Some(&mut first[0])
} else {
None
}
}
}
fn main() {
let mut v: Vec<usize> = vec![
100, 200, 300, 400, 500, 600,
110, 210, 310, 410, 510, 610,
120, 220, 320, 420, 520, 620,
130, 230, 330, 430, 530, 630,
];
for x in Iter2DMut::new(&mut v, (4, 6), (2, 4), (1, 1)) {
println!("{}", x);
}
}
这里还有另外一个技巧:我们使用mem::swap
从data
中移出Iter2DMut
字段,以便在其上调用split_at_mut
。我们暂时交换一个虚拟值&mut []
;这是必要的,因为Rust不允许我们(随机地)从(可变地)借来的结构中移出一个值,而不能同时放回一些东西。另一方面,如果我们没有尝试移出data
,而是像split_at_mut
那样直接调用了self.data.split_at_mut(1)
,它将使借阅检查程序失败,因为那样的话,我们就应该借入self.data
它的生存时间与&mut self
方法中的next
参考输入一样长,而不一定与我们需要的'a
生存期一样长。
Edit:这是对在可变引用上创建迭代器的问题的更一般的解释。 Brent's answer显示了如何使用std
中的函数来为您解决不安全的指针操作,以解决此特定问题。
迭代可变引用需要不安全的代码somewhere。要了解原因,请考虑一个更简单的示例:
struct MyIterMut<'a, T: 'a> {
data: &'a mut [T],
index: usize,
}
impl<'a, T: 'a> Iterator for MyIterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
unimplemented!()
}
}
fn main() {
let mut data = vec![1, 2, 3, 4];
let a;
let b;
{
let mut iter = MyIterMut { data: &mut data, index: 0 };
a = iter.next();
b = iter.next();
}
// a and b are usable after the iterator is dropped, as long as data is still around
println!("{:?}, {:?}", a, b);
}
该迭代器的用户被允许在删除迭代器后使用其值,只要原始数据仍然有效。这用next
的类型表示,加上明确的生命周期,为:
fn next<'n>(&'n mut self) -> Option<&'a mut T>
'n
和'a
之间没有关系,因此使用迭代器的代码可以不受限制地自由使用它们。这就是您想要的。
假设我们这样实现next()
:
fn next(&mut self) -> Option<&'a mut T> {
Some(&mut self.data[0])
}
此实现是错误的,并且会导致您在代码中看到的相同错误。如果编译器允许,则上面的main
函数将具有两个变量a
和b
,它们都包含对同一数据的可变引用。这是未定义的行为,借用检查器可以防止它发生。
防止这种情况的方法是,注意到您是从self
借用的,它的生存期与数据的生存期无关。编译器无法知道是否将多次调用next
或调用者将如何处理数据。它只知道没有足够的信息来确定它是否安全。
但是,您可能会争辩,您不需要[[需要借用整个self
;您只需要从切片中借用该单个项目。不幸的是,当您借用一个结构片段时,您将借用整个结构。无法表示这种调用next()
的类型将借用索引0,而下一个将借用索引1等。
impl<'a, T: 'a> Iterator for MyIterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.data.len() {
let index = self.index;
self.index += 1;
let ptr = self.data.as_mut_ptr();
Some(unsafe { &mut *ptr.add(index) })
} else {
None
}
}
}
由于迭代器采用对&mut
的data
引用,因此无法构造它的多个实例。如果可能的话,仍然会有不确定行为的可能性,但是Rust借用检查器会为我们解决这个问题。
[每当使用不安全的代码时,您都必须非常小心如何实施假定的不变式。根据您的情况,您还需要确保由于
shape
与数据大小不匹配而无法创建无效的指针。如果发生这种情况,您可能应该panic!
,它总是比未定义的行为更可取。我希望这个答案的长度能传达出您不应轻描淡写。