【问题标题】:Equivalence of iterating over filter and continue condition within iteration在迭代中迭代过滤器和继续条件的等价性
【发布时间】:2020-09-09 04:51:24
【问题描述】:

为了让我的代码更清晰易读,我将几个数据结构提取到一个单独的结构中:

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

【问题讨论】:

标签: rust iterator closures immutability borrow-checker


【解决方案1】:

版本2的问题:

for (index, &item) in w.x.iter().enumerate()
    .filter(|&(_, &item)| !w.y.contains(&item)) {
    w.z[index] += 1;
}

filter()的闭包通过引用捕获w,也就是说,它拥有一个&amp;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 = &amp;w.y;,您只能捕获y。现在你必须将闭包标记为move,否则你会捕获&amp;y,而y 是一个临时的,不起作用。

版本3的问题类似:调用成员借用self,即&amp;w,所以以后不能修改。但是类似的解决方法是行不通的,因为如果你的iter_not_in_y() 实现使用self.z 会发生什么?您可以轻松获得未定义的行为。

【讨论】:

  • 谢谢你,这澄清了。你认为未来可以扩展borrowck以支持版本2,甚至可能是版本doing_it_inside_the_struct
  • @vandenheuvel 版本 3 的问题是 local reasoning 原则,即 borrowck 不应该看 impl。其他函数,只有声明,谁知道你在方法中做了什么?版本 2... 在闭包中使用 w.y 将仅借用成员而不是整个结构是合理的。其实我觉得挺好的!
  • @vandenheuvel 确实很合理! github.com/rust-lang/rfcs/blob/master/text/…
猜你喜欢
  • 2018-03-16
  • 2023-04-02
  • 2017-05-19
  • 2020-04-08
  • 2011-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-26
相关资源
最近更新 更多