在迭代中迭代过滤器和继续条件的等价性

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.吗?可以通过选择不同的设计来避免这个问题吗?

Playground

版本 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.yw.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 会怎样?您很容易遇到未定义的行为。