简体   繁体   English

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

[英]How can I move a captured variable into a closure within a closure?

This code is an inefficient way of producing a unique set of items from an iterator.此代码是一种从迭代器生成唯一项集的低效方式。 To accomplish this, I am attempting to use a Vec to keep track of values I've seen.为了实现这一点,我试图使用Vec来跟踪我所看到的值。 I believe that this Vec needs to be owned by the innermost closure:我相信这个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);
}

However, compilation fails with:但是,编译失败:

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

This is a little surprising, but isn't a bug.这有点令人惊讶,但不是错误。

flat_map takes a FnMut as it needs to call the closure multiple times. flat_map需要一个FnMut因为它需要多次调用闭包。 The code with move on the inner closure fails because that closure is created multiple times, once for each inner_numbers .在内部闭包上使用move的代码失败,因为该闭包被多次创建,每个inner_numbers一次。 If I write the closures in explicit form (ie a struct that stores the captures and an implementation of one of the closure traits) your code looks (a bit) like如果我以显式形式编写闭包(即存储捕获的结构和闭包特征之一的实现),您的代码看起来(有点)像

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 { ... }

Which makes the illegality clearer: attempting to move out of the &mut OuterClosure variable.这使得非法性更加清晰:试图移出&mut OuterClosure变量。


Theoretically , just capturing a mutable reference is sufficient, since the seen is only being modified (not moved) inside the closure.理论上,只捕获一个可变引用就足够了,因为seen的只是在闭包内被修改(而不是移动)。 However things are too lazy for this to work...然而,事情太懒了,无法正常工作......

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| |         })
  | |_________^

Removing the move s makes the closure captures work like删除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> { ... }

(I've named the &mut self lifetime in this one, for pedagogical purposes.) (出于教学目的,我在此命名了&mut self生命周期。)

This case is definitely more subtle.这个案子肯定更微妙。 The FilterMap iterator stores the closure internally, meaning any references in the closure value (that is, any references it captures) have to be valid as long as the FilterMap values are being thrown around, and, for &mut references, any references have to be careful to be non-aliased. FilterMap迭代器在内部存储闭包,这意味着只要FilterMap值被抛出,闭包值中的任何引用(即它捕获的任何引用)都必须是有效的,并且对于&mut引用,任何引用都必须是注意不要出现锯齿。

The compiler can't be sure flat_map won't, eg store all the returned iterators in a Vec<FilterMap<...>> which would result in a pile of aliased &mut s... very bad!编译器不能确定flat_map不会,例如将所有返回的迭代器存储在Vec<FilterMap<...>> ,这会导致一堆别名&mut s... 非常糟糕! I think this specific use of flat_map happens to be safe, but I'm not sure it is in general, and there's certainly functions with the same style of signature as flat_map (eg map ) would definitely be unsafe .认为flat_map这种特定使用碰巧是安全的,但我不确定它是一般的,并且肯定有与flat_map具有相同签名风格的flat_map (例如map )肯定是unsafe (In fact, replacing flat_map with map in the code gives the Vec situation I just described.) (实际上,代码中用map替换flat_map给出了我刚刚描述的Vec情况。)

For the error message: self is effectively (ignoring the struct wrapper) &'b mut (&'a mut Vec<i32>) where 'b is the lifetime of &mut self reference and 'a is the lifetime of the reference in the struct .对于错误消息: self是有效的(忽略结构包装器) &'b mut (&'a mut Vec<i32>)其中'b&mut self引用的生命周期, 'astruct引用的生命周期. Moving the inner &mut out is illegal: can't move an affine type like &mut out of a reference (it would work with &Vec<i32> , though), so the only choice is to reborrow.将内部&mut移出是非法的:不能将像&mut这样的仿射类型移出引用(不过它可以与&Vec<i32> ),因此唯一的选择是重新借用。 A reborrow is going through the outer reference and so cannot outlive it, that is, the &mut *self.seen reborrow is a &'b mut Vec<i32> , not a &'a mut Vec<i32> .重新借用正在通过外部引用,因此不能超过它,也就是说, &mut *self.seen重新借用是&'b mut Vec<i32> ,而不是&'a mut Vec<i32>

This makes the inner closure have type InnerClosure<'b> , and hence the call_mut method is trying to return a FilterMap<..., InnerClosure<'b>> .这使得内部闭包具有InnerClosure<'b>类型,因此call_mut方法试图返回一个FilterMap<..., InnerClosure<'b>> Unfortunately, the FnMut trait defines call_mut as just不幸的是, FnMut traitcall_mut定义为

pub trait FnMut<Args>: FnOnce<Args> {
    extern "rust-call" fn call_mut(&mut self, args: Args) -> Self::Output;
}

In particular, there's no connection between the lifetime of the self reference itself and the returned value, and so it is illegal to try to return InnerClosure<'b> which has that link.特别是, self引用本身的生命周期与返回值之间没有联系,因此尝试返回具有该链接的InnerClosure<'b>是非法的。 This is why the compiler is complaining that the lifetime is too short to be able to reborrow.这就是编译器抱怨生命周期太短而无法重新借用的原因。

This is extremely similar to the Iterator::next method, and the code here is failing for basically the same reason that one cannot have an iterator over references into memory that the iterator itself owns.这与Iterator::next方法极其相似,这里的代码失败的原因基本上与不能拥有迭代器对迭代器本身拥有的内存的引用相同。 (I imagine a "streaming iterator" (iterators with a link between &mut self and the return value in next ) library would be able to provide a flat_map that works with the code nearly written: would need "closure" traits with a similar link.) (我想象一个“流迭代器” (在&mut selfnext的返回值之间有链接的迭代器)库将能够提供一个flat_map ,它可以与几乎编写的代码一起使用:需要具有类似链接的“闭包”特征。 )

Work-arounds include:变通办法包括:

  • the RefCell suggested by Renato Zannon, which allows seen to be borrowed as a shared & .所述RefCell建议雷纳托Zannon,其允许seen要借用作为共享& The desugared closure code is basically the same other than changing the &mut Vec<i32> to &Vec<i32> .脱糖闭合代码基本上不是改变相同的其他&mut Vec<i32>&Vec<i32> This change means "reborrow" of the &'b mut &'a RefCell<Vec<i32>> can just be a copy of the &'a ... out of the &mut .此更改意味着&'b mut &'a RefCell<Vec<i32>>可以只是&'a ...的副本&mut It's a literal copy, so the lifetime is retained.这是一个文字副本,因此保留了生命周期。
  • avoiding the laziness of iterators, to avoid returning the inner closure, specifically .collect::<Vec<_>>() ing inside the loop to run through the whole filter_map before returning.避免迭代器的惰性,避免返回内部闭包,特别是.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);
}

I imagine the RefCell version is more efficient.我想RefCell版本更有效。

It seems that the borrow checker is getting confused at the nested closures + mutable borrow.似乎借用检查器对嵌套闭包 + 可变借用感到困惑。 It might be worth filing an issue.可能值得提出问题。 Edit: See huon's answer for why this isn't a bug.编辑:有关为什么这不是错误的原因,请参阅huon 的回答

As a workaround, it's possible to resort to RefCell here:作为一种解决方法,可以在此处使用RefCell

use std::cell::RefCell;

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

    let seen_cell = RefCell::new(seen);

    let a: Vec<_> = items
        .iter()
        .flat_map(|inner_numbers| {
            inner_numbers.iter().filter_map(|&number| {
                let mut borrowed = seen_cell.borrow_mut();

                if !borrowed.contains(&number) {
                    borrowed.push(number);
                    Some(number)
                } else {
                    None
                }
            })
        })
        .collect();

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

I came across this question after I ran into a similar issue, using flat_map and filter_map .在遇到类似问题后,我遇到了这个问题,使用flat_mapfilter_map I solved it by moving the filter_map outside the flat_map closure.我通过将filter_map flat_map闭包之外解决了这个问题。

Using your example:使用您的示例:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM