无法移出`FnMut`闭包中的捕获变量

时间:2018-10-29 04:53:13

标签: rust

pub fn create_future(
    notificator: mpsc::Sender<usize>,
    proxy: Proxy,
) -> impl Future<Item = (), Error = ()> {
    proxy.something()
        .and_then(move |sub| {
            sub.for_each(move |a| { // <---- Closure A
                proxy.something_else(a)
                    .and_then(move |b| { // <---- Closure B
                        notificator.send(b.len());  // <---- Error!
                        Ok(())
                    })
                    .or_else(|e| {
                        panic!("oops {}", e);
                        Ok(())
                    })
            })
        })
        .map_err(|e| {
            ()
        })
}

这不会编译,因为

.and_then(move |b| {
          ^^^^^^^^^ cannot move out of captured outer variable in an `FnMut` closure

我对错误的理解是:

  1. 封存B为FnMut,它通过获取所有权notificator
  2. 在封顶B中,send再次需要拥有所有权
  3. 现在send和闭包B都在修改notificator,因此出现了错误。

我的理解正确吗?我该如何解决这个问题?

1 个答案:

答案 0 :(得分:3)

嵌套闭包很棘手。

考虑一下:

fn use_a_fn_multiple_times(f: impl Fn(String)) {
    f("foo".to_owned());
    f("bar".to_owned());
}

fn use_fn_once(f: impl FnOnce() -> Vec<u8>) {
    println!("Bytes: {:?}", f());
}

fn main() {
  use_a_fn_multiple_times(|a: String| {
    use_fn_once(move || a.into_bytes());
  });
}

Playground

请注意,内部闭包通过移动捕获了a。这可以。外部闭包拥有a并可以执行其所需的操作,包括将其移入内部闭包(由于它消耗了捕获的值,因此是FnOnce)。

多次调用外部闭包,每次使用新字符串,并且每次创建捕获该字符串的新内部闭包。

但是,如果要捕获的内容来自更远的地方怎么办?

fn use_a_fn_multiple_times(f: impl Fn(String)) {
    f("foo".to_owned());
    f("bar".to_owned());
}

fn use_fn_once(f: impl FnOnce() -> Vec<u8>) {
    println!("Bytes: {:?}", f());
}

fn main() {
  let outer_s = "see:".to_owned();

  use_a_fn_multiple_times(|a: String| {
    use_fn_once(move || {
        let mut v = outer_s.into_bytes();
        v.extend(a.into_bytes());
        v
    });
  });
}

Playground

然后您将看到错误(FnFnMut除外,这对问题无关紧要)。内部闭包是在每次调用外部闭包时重新创建的(必须如此,因为它每次必须捕获a,但每次尝试通过移动来捕获outer_s。这行不通;第一次之后,outer_s被移出并因此无效。

要将其映射回您的代码,请说“ closure B捕获notificator”是错误的,因为不仅有一个ClosureB。有很多需要,但是经常嵌套{{1} }和and_then调用将最终出现在该段代码中。但是只有一个人可以通过移动捕获。

因此,要解决此问题,您需要确保只有一个封闭B,或者确保每个人都有足够的for_each

第一种方法是将闭包从嵌套上下文中拉出。

mpsc::Sender

除非这是行不通的,否则从现在开始,闭包A面临相同的问题,因此您必须执行多次:

let closure_b = move |b| {
    notificator.send(b.len());
    Ok(())
};
proxy.something()
    .and_then(move |sub| {
        sub.for_each(move |a| { // <---- Closure A
            proxy.something_else(a)
                .and_then(closure_b)
                .or_else(|e| {
                    panic!("oops {}", e);
                    Ok(())
                })
        })
    })
    .map_err(|e| {
        ()
    })

第二种方法涉及许多let closure_b = move |b| { notificator.send(b.len()); Ok(()) }; let closure_a = move |a| { proxy.something_else(a) .and_then(closure_b) .or_else(|e| { panic!("oops {}", e); Ok(()) }) }; proxy.something() .and_then(move |sub| { sub.for_each(closure_a) }) .map_err(|e| { () }) 调用,并且由于我无法类型检查您的代码,因此我不会尝试编写它。

尽管一切都说完了,但是您的代码仍然会失败,因为您要移出clone(),同时还要尝试使用它。