这有点令人惊讶,但不是错误。
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| | })
| |_________^
删除moves 使闭包捕获工作起来像
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<...>> 中,这将导致一堆别名 &muts... 非常糟糕!我认为flat_map 的这种特定用法恰好是安全的,但我不确定它是否一般,并且肯定有与flat_map 具有相同签名样式的函数(例如@987654343 @) 肯定是unsafe。 (实际上,将代码中的flat_map 替换为map 就是我刚才描述的Vec 的情况。)
对于错误消息:self 有效(忽略结构包装器)&'b mut (&'a mut Vec<i32>) 其中'b 是&mut self 引用的生命周期,'a 是struct 中引用的生命周期。将内部&mut 移出是非法的:不能将&mut 之类的仿射类型移出引用(尽管它可以与&Vec<i32> 一起使用),因此唯一的选择是重新借用。 reborrow 是通过外部引用进行的,因此不能超过它,也就是说,&mut *self.seen reborrow 是 &'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,它与几乎编写的代码一起工作:需要“闭包”特征类似的链接。)
解决方法包括:
- Renato Zannon 建议的
RefCell,它允许将seen 作为共享& 借用。除了将&mut Vec<i32> 改为&Vec<i32> 之外,脱糖的闭包代码基本相同。此更改意味着&'b mut &'a RefCell<Vec<i32>> 的“重借”可以只是&mut 之外的&'a ... 的副本。它是文字副本,因此保留了生命周期。
- 避免迭代器的惰性,避免返回内部闭包,特别是循环内部的
.collect::<Vec<_>>()ing 在返回之前遍历整个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 版本效率更高。