在迭代中迭代过滤器和继续条件的等价性
Equivalence of iterating over filter and continue condition within iteration
为了让我的代码更易读,我将几个数据结构提取到一个单独的结构中:
struct S {
x: Vec<i32>,
y: HashSet<i32>,
z: Vec<i32>,
}
它只存在于一个方法调用及其子调用中:
fn main() {
let mut w = S { x: vec![], y: HashSet::new(), z: vec![], };
do_part_of_the_work(&mut w);
}
fn do_part_of_the_work(w: &mut S) {
// 1. Works
for (index, &item) in w.x.iter().enumerate() {
if w.y.contains(&item) {
continue;
}
w.z[index] += 1;
}
// 2. Seems equivalent to 1. but doesn't work
for (index, &item) in w.x.iter().enumerate()
.filter(|&(_, &item)| !w.y.contains(&item)) {
w.z[index] += 1;
}
// 3. Seems equivalent to 2. and doesn't work either
for (index, &item) in w.iter_not_in_y() {
w.z[index] += 1;
}
}
impl S {
fn iter_not_in_y(&self) -> impl Iterator<Item = (usize, &i32)> {
self.x.iter().enumerate().filter(move |&(_, &item)| !self.y.contains(&item))
}
}
我基本上是在尝试以代码块 3.
的形式执行代码块 1.
所做的事情,将 2.
作为一个不起作用的中间步骤,尽管这些看起来相等的。如果 S
的所有属性都是局部变量,似乎所有三个代码块都可以工作。
在 impl
中移动代码块也没有让我走得太远:
impl S {
fn doing_it_inside_the_struct(&mut self) {
// Doing 3. inside the struct instead, doesn't work either
for (index, &item) in self.iter_not_in_y() {
self.z[index] += 1;
}
}
}
为什么阻止 2.
不起作用?不等于1.
吗?可以通过选择不同的设计来避免这个问题吗?
版本 2 的问题:
for (index, &item) in w.x.iter().enumerate()
.filter(|&(_, &item)| !w.y.contains(&item)) {
w.z[index] += 1;
}
是filter()
的闭包通过引用捕获了w
,也就是持有了一个&w
。这意味着只要这个闭包还活着,整个 w
就会被借用。然后,当您尝试可变地借用 w.z
时,编译器会失败。
第一个版本中的代码使用 w.y
和 w.z
分开借用,并且 w
本身从不借用,所以它有效。
解决方案是编写闭包以仅捕获 w.y
而不是 w
。不幸的是,没有一个简单而好的语法。我能写的更好的是:
for (index, &item) in w.x.iter().enumerate()
.filter({
let y = &w.y;
move |&(_, &item)| !y.contains(&item)
}) {
w.z[index] += 1;
}
使用 let y = &w.y;
,您只能捕获 y
。现在您必须将闭包标记为 move
,否则您将捕获 &y
,并且 y
是一个无法工作的临时值。
版本 3 的问题类似:调用成员借用 self
,即 &w
,因此您之后无法修改它。但是类似的变通方法是行不通的,因为如果您的 iter_not_in_y()
实现使用 self.z
会怎样?您很容易遇到未定义的行为。
为了让我的代码更易读,我将几个数据结构提取到一个单独的结构中:
struct S {
x: Vec<i32>,
y: HashSet<i32>,
z: Vec<i32>,
}
它只存在于一个方法调用及其子调用中:
fn main() {
let mut w = S { x: vec![], y: HashSet::new(), z: vec![], };
do_part_of_the_work(&mut w);
}
fn do_part_of_the_work(w: &mut S) {
// 1. Works
for (index, &item) in w.x.iter().enumerate() {
if w.y.contains(&item) {
continue;
}
w.z[index] += 1;
}
// 2. Seems equivalent to 1. but doesn't work
for (index, &item) in w.x.iter().enumerate()
.filter(|&(_, &item)| !w.y.contains(&item)) {
w.z[index] += 1;
}
// 3. Seems equivalent to 2. and doesn't work either
for (index, &item) in w.iter_not_in_y() {
w.z[index] += 1;
}
}
impl S {
fn iter_not_in_y(&self) -> impl Iterator<Item = (usize, &i32)> {
self.x.iter().enumerate().filter(move |&(_, &item)| !self.y.contains(&item))
}
}
我基本上是在尝试以代码块 3.
的形式执行代码块 1.
所做的事情,将 2.
作为一个不起作用的中间步骤,尽管这些看起来相等的。如果 S
的所有属性都是局部变量,似乎所有三个代码块都可以工作。
在 impl
中移动代码块也没有让我走得太远:
impl S {
fn doing_it_inside_the_struct(&mut self) {
// Doing 3. inside the struct instead, doesn't work either
for (index, &item) in self.iter_not_in_y() {
self.z[index] += 1;
}
}
}
为什么阻止 2.
不起作用?不等于1.
吗?可以通过选择不同的设计来避免这个问题吗?
版本 2 的问题:
for (index, &item) in w.x.iter().enumerate()
.filter(|&(_, &item)| !w.y.contains(&item)) {
w.z[index] += 1;
}
是filter()
的闭包通过引用捕获了w
,也就是持有了一个&w
。这意味着只要这个闭包还活着,整个 w
就会被借用。然后,当您尝试可变地借用 w.z
时,编译器会失败。
第一个版本中的代码使用 w.y
和 w.z
分开借用,并且 w
本身从不借用,所以它有效。
解决方案是编写闭包以仅捕获 w.y
而不是 w
。不幸的是,没有一个简单而好的语法。我能写的更好的是:
for (index, &item) in w.x.iter().enumerate()
.filter({
let y = &w.y;
move |&(_, &item)| !y.contains(&item)
}) {
w.z[index] += 1;
}
使用 let y = &w.y;
,您只能捕获 y
。现在您必须将闭包标记为 move
,否则您将捕获 &y
,并且 y
是一个无法工作的临时值。
版本 3 的问题类似:调用成员借用 self
,即 &w
,因此您之后无法修改它。但是类似的变通方法是行不通的,因为如果您的 iter_not_in_y()
实现使用 self.z
会怎样?您很容易遇到未定义的行为。