【问题标题】:Why can't a variable be moved out of a closure?为什么不能将变量移出闭包?
【发布时间】:2020-05-04 22:46:32
【问题描述】:

我有以下功能:

pub fn map_option<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
    Box::new(move |opt_a: Option<A>| {
        opt_a.map(|a| a2b(a))
    })
}

但是,这很难写。我从一些更简单但不起作用的东西开始,但我不明白为什么它不起作用。

  1. 这是我的第一个版本:

    pub fn map_option_1<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
        Box::new(|opt_a: Option<A>| {
            opt_a.map(a2b)
        })
    }
    

    这给了我以下错误:

    error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
      --> src/lib.rs:11:19
       |
    9  | pub fn map_option_1<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
       |                                              --- captured outer variable
    10 |     Box::new(|opt_a: Option<A>| {
    11 |         opt_a.map(a2b)
       |                   ^^^ move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
    
  2. 我认为我可能需要move 闭包,以便它获得a2b 的所有权:

    pub fn map_option_2<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
        Box::new(move |opt_a: Option<A>| {
            opt_a.map(a2b)
        })
    }
    

    但是,这也不起作用。它失败并显示以下消息:

    error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
      --> src/lib.rs:17:19
       |
    15 | pub fn map_option_2<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
       |                                              --- captured outer variable
    16 |     Box::new(move |opt_a: Option<A>| {
    17 |         opt_a.map(a2b)
       |                   ^^^ move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
    

    这个错误消息说a2b 没有实现Copy,我想这是有道理的,但我不知道如何解决它。

  3. 出于绝望,我尝试了以下方法:

    pub fn map_option_3<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
        Box::new(|opt_a: Option<A>| {
            opt_a.map(|a| a2b(a))
        })
    }
    

    这至少给了我一个不同的错误:

    error[E0373]: closure may outlive the current function, but it borrows `a2b`, which is owned by the current function
      --> src/lib.rs:22:14
       |
    22 |     Box::new(|opt_a: Option<A>| {
       |              ^^^^^^^^^^^^^^^^^^ may outlive borrowed value `a2b`
    23 |         opt_a.map(|a| a2b(a))
       |                       --- `a2b` is borrowed here
       |
    note: closure is returned here
      --> src/lib.rs:22:5
       |
    22 | /     Box::new(|opt_a: Option<A>| {
    23 | |         opt_a.map(|a| a2b(a))
    24 | |     })
       | |______^
    help: to force the closure to take ownership of `a2b` (and any other referenced variables), use the `move` keyword
       |
    22 |     Box::new(move |opt_a: Option<A>| {
       |              ^^^^^^^^^^^^^^^^^^^^^^^
    

    我猜,所有权的问题是有道理的。这就是让我找到上述实际可行的解决方案的原因。

  4. 我尝试过的另一件事无效:

    pub fn map_option_4<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
        Box::new(|opt_a: Option<A>| {
            opt_a.map(move |a| a2b(a))
        })
    }
    

    这给了我以下错误:

    error[E0507]: cannot move out of `a2b`, a captured variable in an `Fn` closure
      --> src/lib.rs:29:19
       |
    27 | pub fn map_option_4<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
       |                                              --- captured outer variable
    28 |     Box::new(|opt_a: Option<A>| {
    29 |         opt_a.map(move |a| a2b(a))
       |                   ^^^^^^^^ ---
       |                   |        |
       |                   |        move occurs because `a2b` has type `std::boxed::Box<dyn std::ops::Fn(A) -> B>`, which does not implement the `Copy` trait
       |                   |        move occurs due to use in closure
       |                   move out of `a2b` occurs here
    

这是一个playground,其中包含这些功能。


我认为我对生命周期和所有权的理解不够好,无法了解这些功能为何会失败。

我可以理解map_option_1map_option_3 是如何失败的,因为move 没有被用来明确移动所有权,但我很惊讶map_option_2map_option_4 失败了。

非常很惊讶map_option_2 不起作用,但实际的map_option 功能起作用。对我来说,这些实际上是相同的功能。

为什么每个map_option_X 函数都无法编译??

【问题讨论】:

  • 我还收到类似于以下内容的警告:trait objects without an explicit dyn` 已弃用. It recommends using types like Box B>` 而不是 Box&lt;Fn(A) -&gt; B&gt;。我实际上并没有检查这是否相关。
  • dyn 的存在与否不会改变程序的语义。
  • 我应该补充一点,这个问题的灵感来自以下 Codewars 问题:codewars.com/kata/5922543bf9c15705d0000020/rust

标签: function lambda rust move-semantics lifetime


【解决方案1】:

我认为我可能需要move 闭包,以便它获得a2b 的所有权

没错,您确实需要外部封盖上的move。如果没有move,闭包将通过引用捕获a2b。但是,a2b 是一个本地参数,返回一个引用本地的闭包是无效的。

move 添加到内部闭包会导致错误,因为该函数返回一个Fn 闭包。对于这个论点,让我们考虑这个map_option_5 函数:

pub fn map_option_5<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
    Box::new(move |opt_a: Option<A>| {
        opt_a.map(move |a| a2b(a))
    })
}

如果内部闭包按值捕获a2b,而外部闭包也是move 闭包,则两个闭包最终都会按值捕获a2b。根据所有权规则,一次只有一个闭包可能拥有a2b,因此当调用外部闭包时,它会将a2b移出自身(解构外部闭包)并进入内部闭包(其中仅适用于FnOnce 闭包,因为它们采用self 而不是&amp;mut self&amp;self)。错误消息的原因是我们返回的是 Fn 闭包,而不是 FnOnce 闭包。我们确实可以通过返回一个FnOnce 闭包来解决这个问题(但它不能被多次调用):

pub fn map_option_5a<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<FnOnce(Option<A>) -> Option<B>> {
    Box::new(move |opt_a: Option<A>| {
        opt_a.map(move |a| a2b(a))
    })
}

现在,让我们讨论一下为什么map_option 有效而map_option_2 无效。问题源于Option::map 拥有闭包参数的所有权。因此,我们最终会遇到类似于上述map_option_5 的情况。 Option::map 接受FnOnce,因为它最多只需要调用一次。将a2b 更改为Box&lt;FnOnce(A) -&gt; B&gt; 也无济于事,因为它实际上可以在对map 的许多调用中使用。

有一种方法可以避免内部闭包:将对a2b 的引用传递给map。这是因为

  1. Box&lt;F&gt; where F: Fn&lt;A&gt; implements Fn&lt;A&gt;
  2. &amp;F where F: Fn&lt;A&gt; implements FnOnce&lt;A&gt;(还有Fn&lt;A&gt;,尽管在这里无关紧要)。
pub fn map_option_2a<A: 'static, B: 'static> (a2b: Box<Fn(A) -> B>) -> Box<Fn(Option<A>) -> Option<B>> {
    Box::new(move |opt_a: Option<A>| {
        opt_a.map(&a2b)
    })
}

闭包仍然拥有a2b 的所有权,但它在调用时不会消耗它,因此可以多次调用闭包。

map_option 有效,因为它的外部闭包不需要消耗a2b。内部闭包通过外部闭包的引用捕获a2b。这是可行的,因为calling an Fn 闭包只需要对闭包的共享引用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-24
    • 1970-01-01
    • 1970-01-01
    • 2014-04-12
    • 1970-01-01
    • 2012-03-04
    • 1970-01-01
    • 2020-09-04
    相关资源
    最近更新 更多