此代码是从迭代器生成一组唯一项的低效率方法。为此,我正在尝试使用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
可以使闭包捕获像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
引用的生存期,而'a
是struct
中的引用生存期。将内部&mut
移出是非法的:无法将像&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 self
和next
中的返回值之间具有链接的迭代器)库将能够提供一个flat_map
,该RefCell
与几乎已编写的代码一起工作:将需要具有类似链接的“closure”特征。)解决方法包括:
seen
,它允许&
作为共享的&mut Vec<i32>
借用。除将&Vec<i32>
更改为&'b mut &'a RefCell<Vec<i32>>
之外,已终止修饰的关闭代码基本相同。此更改意味着&'a ...
的“重新借用”可以只是&mut
中的.collect::<Vec<_>>()
的副本。这是一个文字副本,因此保留了生存期。 filter_map
在返回之前遍历整个RefCell
。 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);
}
我认为ojit_code版本更有效。
关于closures - 如何将捕获的变量移动到闭包内的闭包中?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28521637/