此代码是从迭代器生成一组唯一项的低效率方法。为此,我正在尝试使用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引用的生存期,而'astruct中的引用生存期。将内部&mut移出是非法的:无法将像&mut的仿射类型移出引用(尽管它可以与&Vec<i32>一起使用),所以唯一的选择是重新借用。重新借阅正在外部引用中进行,因此不能超过它,也就是说,&mut *self.seen重新借阅是&'b mut Vec<i32>,而不是&'a mut Vec<i32>

这使得内部闭包的类型为InnerClosure<'b>,因此call_mut方法正尝试返回FilterMap<..., InnerClosure<'b>>。不幸的是,the FnMut traitcall_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与几乎已编写的代码一起工作:将需要具有类似链接的“closure”特征。)

解决方法包括:
  • 是Renato Zannon建议的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/

    10-12 07:33