gpt4 book ai didi

closures - 如何将捕获的变量移动到闭包内的闭包中?

转载 作者:行者123 更新时间:2023-11-29 08:20:02 25 4
gpt4 key购买 nike

此代码是一种从迭代器生成唯一项集的低效方式。为此,我尝试使用 Vec跟踪我看到的值。我相信这个Vec需要由最里面的闭包拥有:

fn main() {
let mut seen = vec![];
let items = vec![vec![1i32, 2], vec![3], vec![1]];

let a: Vec<_> = items
.iter()
.flat_map(move |inner_numbers| {
inner_numbers.iter().filter_map(move |&number| {
if !seen.contains(&number) {
seen.push(number);
Some(number)
} else {
None
}
})
})
.collect();

println!("{:?}", a);
}

但是,编译失败:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
--> src/main.rs:8:45
|
2 | let mut seen = vec![];
| -------- captured outer variable
...
8 | inner_numbers.iter().filter_map(move |&number| {
| ^^^^^^^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

最佳答案

这有点令人惊讶,但不是错误。

flat_map 需要一个 FnMut因为它需要多次调用闭包。带有 move 的代码内部闭包失败,因为该闭包被创建多次,每次创建一次 inner_numbers .如果我以显式形式编写闭包(即存储捕获的结构和闭包特征之一的实现),您的代码看起来(有点)像

struct OuterClosure {
seen: Vec<i32>
}
struct InnerClosure {
seen: Vec<i32>
}
impl FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure> for OuterClosure {
fn call_mut(&mut self, (inner_numbers,): &Vec<i32>) -> iter::FilterMap<..., InnerClosure> {
let inner = InnerClosure {
seen: self.seen // uh oh! a move out of a &mut pointer
};
inner_numbers.iter().filter_map(inner)
}
}
impl FnMut(&i32) -> Option<i32> for InnerClosure { ... }

这使得非法性更加清晰:试图搬出 &mut OuterClosure多变的。

理论上,只捕获一个可变引用就足够了,因为 seen仅在闭包内被修改(未移动)。然而,事情太懒了,无法正常工作......

error: lifetime of `seen` is too short to guarantee its contents can be safely reborrowed
--> src/main.rs:9:45
|
9 | inner_numbers.iter().filter_map(|&number| {
| ^^^^^^^^^
|
note: `seen` would have to be valid for the method call at 7:20...
--> src/main.rs:7:21
|
7 | let a: Vec<_> = items.iter()
| _____________________^
8 | | .flat_map(|inner_numbers| {
9 | | inner_numbers.iter().filter_map(|&number| {
10| | if !seen.contains(&number) {
... |
17| | })
18| | .collect();
| |__________________^
note: ...but `seen` is only valid for the lifetime as defined on the body at 8:34
--> src/main.rs:8:35
|
8 | .flat_map(|inner_numbers| {
| ___________________________________^
9 | | inner_numbers.iter().filter_map(|&number| {
10| | if !seen.contains(&number) {
11| | seen.push(number);
... |
16| | })
17| | })
| |_________^

删除 move s 使闭包捕获像
struct OuterClosure<'a> {
seen: &'a mut Vec<i32>
}
struct InnerClosure<'a> {
seen: &'a mut Vec<i32>
}
impl<'a> FnMut(&Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> for OuterClosure<'a> {
fn call_mut<'b>(&'b mut self, inner_numbers: &Vec<i32>) -> iter::FilterMap<..., InnerClosure<??>> {
let inner = InnerClosure {
seen: &mut *self.seen // can't move out, so must be a reborrow
};
inner_numbers.iter().filter_map(inner)
}
}
impl<'a> FnMut(&i32) -> Option<i32> for InnerClosure<'a> { ... }

(出于教学目的,我在此命名了 &mut self 生命周期。)

这个案子肯定更微妙。 FilterMap迭代器在内部存储闭包,这意味着闭包值中的任何引用(即它捕获的任何引用)都必须是有效的,只要 FilterMap值被抛出,并且,对于 &mut引用,任何引用都必须小心不要出现锯齿。

编译器无法确定 flat_map不会,例如将所有返回的迭代器存储在 Vec<FilterMap<...>> 中这会导致一堆别名 &mut s……很糟糕!我觉得这个具体用途 flat_map碰巧是安全的,但我不确定它是否一般,并且肯定有与 flat_map 具有相同签名风格的函数(例如 map )肯定是 unsafe . (实际上,在代码中用 flat_map 替换 map 给出了我刚刚描述的 Vec 情况。)

对于错误消息: self有效(忽略结构包装器) &'b mut (&'a mut Vec<i32>)哪里 'b&mut self 的生命周期引用和 'astruct 中引用的生命周期.搬家 &mut out 是非法的:不能移动像 &mut 这样的仿射类型出于引用(尽管它可以与 &Vec<i32> 一起使用),因此唯一的选择是重新借用。重新借用正在通过外部引用,因此不能超过它,即 &mut *self.seen再借是 &'b mut Vec<i32> ,不是 &'a mut Vec<i32> .

这使得内闭包具有类型 InnerClosure<'b> ,因此 call_mut方法试图返回 FilterMap<..., InnerClosure<'b>> .不幸的是, the FnMut trait定义 call_mut就像
pub trait FnMut<Args>: FnOnce<Args> {
extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

特别是, self 的生命周期之间没有联系。引用自身和返回值,因此尝试返回 InnerClosure<'b> 是非法的有那个链接。这就是编译器提示生命周期太短而无法重新借用的原因。

这与 Iterator::next 极为相似方法,并且这里的代码失败的原因基本上与不能有迭代器对迭代器本身拥有的内存的引用相同。 (我想象一个 "streaming iterator"(在 &mut selfnext 中的返回值之间有一个链接的迭代器)库将能够提供一个 flat_map 可以与几乎编写的代码一起使用:需要“闭包”特征类似的链接。)

变通办法包括:
  • RefCell由 Renato Zannon 建议,允许 seen作为共享借用& .除了更改 &mut Vec<i32> 之外,脱糖关闭代码基本相同至 &Vec<i32> .此更改意味着“重新借用”&'b mut &'a RefCell<Vec<i32>>可以只是&'a ...的副本出了&mut .这是一个文字副本,因此保留了生命周期。
  • 避免迭代器的惰性,避免返回内部闭包,特别是 .collect::<Vec<_>>()在循环内部运行以贯穿整个 filter_map在回来之前。


  • fn main() {
    let mut seen = vec![];
    let items = vec![vec![1i32, 2], vec![3], vec![1]];

    let a: Vec<_> = items
    .iter()
    .flat_map(|inner_numbers| {
    inner_numbers
    .iter()
    .filter_map(|&number| if !seen.contains(&number) {
    seen.push(number);
    Some(number)
    } else {
    None
    })
    .collect::<Vec<_>>()
    .into_iter()
    })
    .collect();

    println!("{:?}", a);
    }

    我想象 RefCell版本效率更高。

    关于closures - 如何将捕获的变量移动到闭包内的闭包中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57391311/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com