如何使用IntoParallelIterator限制可变和不可变借用

3

我有一个函数,它操作的是一个 Vec<T>,其目的是使用现有项的引用生成新项并将其添加到向量中。 我正在尝试使用 rayon 并行运行新数据的生成。

这是一个最简示例:

use itertools::Itertools;
use rayon::prelude::*;

fn main() {
    let mut foo = Foo {
        data: (0..1000).into_iter().collect(),
    };
    foo.run();
}

struct Foo<T> {
    data: Vec<T>,
}

type Pair<'a, T> = (&'a T, &'a T);

impl<'a, T: Clone + 'a> Foo<T>
where
    Vec<Pair<'a, T>>: IntoParallelIterator<Item = Pair<'a, T>>,
    [T; 2]: IntoParallelIterator,
    Vec<T>: FromParallelIterator<<[T; 2] as IntoParallelIterator>::Item>,
{
    fn run(&'a mut self) {
        let combinations: Vec<Pair<'a, T>> = self
            .data
            .iter()
            .combinations(2)
            .map(|x| (x[0], x[1]))
            .collect();

        let mut new_combinations: Vec<T> = combinations
            .into_par_iter()
            .flat_map(|(a, b)| bar(a, b))
            .collect();

        self.data.append(&mut new_combinations);
    }
}


fn bar<T: Clone>(a: &T, b: &T) -> [T; 2] {
    [a.clone(), b.clone()]
}

您可以在这里找到 Playground 的链接。

构建上面的示例会引发此错误:

error[E0502]: cannot borrow `self.data` as mutable because it is also borrowed as immutable
  --> src/main.rs:36:9
   |
17 |   impl<'a, T: Clone + 'a> Foo<T>
   |        -- lifetime `'a` defined here
...
24 |           let combinations: Vec<Pair<'a, T>> = self
   |  ___________________________----------------___-
   | |                           |
   | |                           type annotation requires that `self.data` is borrowed for `'a`
25 | |             .data
26 | |             .iter()
   | |___________________- immutable borrow occurs here
...
36 |           self.data.append(&mut new_combinations);
   |           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

据我所知,因为我正在收集到一个let new_combinations: Vec<T>中,所以对于 self.data ,不应该有不可变的引用,并且理论上应该能够借用它以可变地附加新的组合。但是,似乎self.data被借用了具有超出此方法范围的生命周期'a。我找不到避免指定生命周期fn run(&'a mut self)的方法,因为我需要指定对self.data项的引用的生命周期不能比创建组合时self的生命周期更长。
有没有一种方法可以使该方法按预期运行,即:1)选择指向self.data项的引用列表,2)并行应用创建新项T的函数,最后3)使用新项更新self.data
请注意,作为解决方法,可以将new_combinations从该方法返回,并单独将其附加到self.data中。
如果能够仅使用迭代器直接操作,而尽可能避免使用collect(),那就太好了。

顺便提一下,0..1000 是一个迭代器,你不需要使用 .into_iter()。请参见 playground - jthulhu
选择一个对self.data中的项目的引用列表[...]使用新项目更新self.data是不可能的,因为从借用检查器的角度来看,更新self.data可能会使引用无效。 - user4815162342
1
@user4815162342 但是 bar() 克隆它们;生命周期只是被错误地注释了。 - Finomnis
“因为在创建组合时,我需要指定self.data中项目的引用的生命周期不能超出self本身。”这是不正确的。对于Rust来说,您不需要生命周期注释就可以知道这一点。它会自动解决。 - Finomnis
1个回答

3
new_combinations 中的元素被克隆了,因此不再从 combinations 借用。但是你的注释中指出了 T: 'a,这意味着 Rust 仍然必须将其视为已借用。
我个人认为,在生命周期注释方面,你过于过度了,可以删除几乎所有注释。在大多数情况下,编译器非常擅长自动确定它们。
此外,你的 trait 限制被编译器提示误导了。一旦指定 T: Clone + Send + Sync,它们都会自动满足。
给你。
use itertools::Itertools;
use rayon::prelude::*;

fn main() {
    let mut foo = Foo {
        data: (0..10).collect(),
    };
    foo.run();
    println!("{:?}", foo.data);
}

struct Foo<T> {
    data: Vec<T>,
}

type Pair<'a, T> = (&'a T, &'a T);

impl<T: Clone + Send + Sync> Foo<T> {
    fn run(&mut self) {
        let combinations: Vec<Pair<T>> = self
            .data
            .iter()
            .combinations(2)
            .map(|x| (x[0], x[1]))
            .collect();

        let mut new_combinations: Vec<T> = combinations
            .into_par_iter()
            .flat_map(|(a, b)| bar(a, b))
            .collect();

        self.data.append(&mut new_combinations);
    }
}

fn bar<T: Clone>(a: &T, b: &T) -> [T; 2] {
    [a.clone(), b.clone()]
}

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8, 1, 9, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8, 2, 9, 3, 4, 3, 5, 3, 6, 3, 7, 3, 8, 3, 9, 4, 5, 4, 6, 4, 7, 4, 8, 4, 9, 5, 6, 5, 7, 5, 8, 5, 9, 6, 7, 6, 8, 6, 9, 7, 8, 7, 9, 8, 9]

此外,实际上并不需要 Pair 类型:
use itertools::Itertools;
use rayon::prelude::*;

fn main() {
    let mut foo = Foo {
        data: (0..10).collect(),
    };
    foo.run();
    println!("{:?}", foo.data);
}

struct Foo<T> {
    data: Vec<T>,
}

impl<T: Clone + Send + Sync> Foo<T> {
    fn run(&mut self) {
        let combinations: Vec<_> = self
            .data
            .iter()
            .combinations(2)
            .map(|x| (x[0], x[1]))
            .collect();

        let mut new_combinations: Vec<T> = combinations
            .into_par_iter()
            .flat_map(|(a, b)| bar(a, b))
            .collect();

        self.data.append(&mut new_combinations);
    }
}

fn bar<T: Clone>(a: &T, b: &T) -> [T; 2] {
    [a.clone(), b.clone()]
}

关于你问题的最后一个部分,如果可能的话,请删除所有.collect()调用:
不幸的是,我认为您无法删除任何collect()。至少不是根据您当前的代码布局。您绝对需要在combinations()into_par_iter()之间进行collect(),并且在append之前您也绝对需要collect(),因为您需要在写入之前释放对self.data的所有引用。

1
感谢你找出最小化所需的Trait边界!当我们仅移除OP指定的多个边界时,编译器会抱怨缺少了Trait[(&T, &T)]: Sized,实际上缺少了T: Send + Sync这一提示。 - user4815162342
是的,在复杂情况下编译器提示确实很难。我知道并行计算需要 Send + Sync,这是显而易见的原因,所以我尝试了一下。 - Finomnis
非常感谢!一开始我确实在想为什么没有人抱怨缺少“发送+同步”,并开始遵循编译器的建议到这里。 - Nick
@Nick 但是作为一个经验法则:除非绝对必要,否则不要注释生命周期。你几乎肯定会注释错误 :) - Finomnis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接