如何将捕获的变量移动到闭包内的闭包中? [英] How can I move a captured variable into a closure within a closure?

查看:30
本文介绍了如何将捕获的变量移动到闭包内的闭包中?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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. I believe that this Vec needs to be owned by the innermost closure:

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

推荐答案

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

This is a little surprising, but isn't a bug.

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

flat_map takes a FnMut as it needs to call the closure multiple times. The code with move on the inner closure fails because that closure is created multiple times, once for each inner_numbers. If I write the closures in explicit form (i.e. 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 { ... }

这使得非法性更加清晰:试图移出 &mut OuterClosure 变量.

Which makes the illegality clearer: attempting to move out of the &mut OuterClosure variable.

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

Theoretically, just capturing a mutable reference is sufficient, since the seen is only being modified (not moved) inside the closure. 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| |         })
  | |_________^

删除 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 生命周期.)

(I've named the &mut self lifetime in this one, for pedagogical purposes.)

这种情况肯定更微妙.FilterMap 迭代器在内部存储闭包,这意味着只要 FilterMap 值是有效的,闭包值中的任何引用(即它捕获的任何引用)都必须有效到处乱扔,而且,对于 &mut 引用,任何引用都必须小心不要出现锯齿.

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.

编译器不能确定 flat_map 不会,例如将所有返回的迭代器存储在 Vec> 中,这会导致一堆别名 &mut ......非常糟糕!我认为 flat_map 的这种特定使用碰巧是安全的,但我不确定它是一般的,并且肯定有与 flat_map(例如map)肯定是unsafe.(实际上,在代码中用 map 替换 flat_map 给出了我刚刚描述的 Vec 情况.)

The compiler can't be sure flat_map won't, e.g. store all the returned iterators in a Vec<FilterMap<...>> which would result in a pile of aliased &muts... very bad! 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 (e.g. map) would definitely be unsafe. (In fact, replacing flat_map with map in the code gives the Vec situation I just described.)

对于错误消息:self 有效(忽略结构包装器)&'b mut (&'a mut Vec<i32>) where 'b&mut self 引用的生命周期,'astruct 中引用的生命周期.将内部 &mut 移出是非法的:不能将像 &mut 这样的仿射类型移出引用(它可以与 &Vec<i32>),所以唯一的选择是重新借用.重新借用正在通过外部引用,因此不能超过它,也就是说,&mut *self.seen 重新借用是一个 &'b mut Vec,而不是 &'a mut 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. 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. 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>.

这使得内部闭包具有 InnerClosure<'b> 类型,因此 call_mut 方法试图返回一个 FilterMap<..., InnerClosure<;'b>>.不幸的是,FnMut trait 定义了call_mut 就像

This makes the inner closure have type InnerClosure<'b>, and hence the call_mut method is trying to return a FilterMap<..., InnerClosure<'b>>. Unfortunately, the FnMut trait defines call_mut as just

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

特别是,self 引用本身的生命周期与返回值之间没有联系,因此尝试返回 InnerClosure<'b> 是非法的有那个链接.这就是为什么编译器会抱怨生命周期太短而无法重新借用.

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. This is why the compiler is complaining that the lifetime is too short to be able to reborrow.

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

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. (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.)

变通方法包括:

  • Renato Zannon 建议的 RefCell,允许将 seen 作为共享的 & 借用.除了将 &mut Vec 更改为 &Vec 之外,脱糖的闭包代码基本相同.此更改意味着 &'b mut &'a RefCell<Vec<i32>> 的重新借用"可以只是 &'a ...&mut 之外.这是一个文字副本,因此保留了生命周期.
  • 避免迭代器的惰性,避免返回内部闭包,特别是.collect::<Vec<_>>()在循环内部运行以运行整个filter_map 返回前.
  • the RefCell suggested by Renato Zannon, which allows seen to be borrowed as a shared &. The desugared closure code is basically the same other than changing the &mut Vec<i32> to &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. 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.
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 版本效率更高.

I imagine the RefCell version is more efficient.

这篇关于如何将捕获的变量移动到闭包内的闭包中?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆