为什么变量不能从闭包中移出?

ill*_*out 3 lambda function lifetime move-semantics rust

我有以下功能:

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))
    })
}
Run Code Online (Sandbox Code Playgroud)

然而,这写起来相当困难。我开始做一些更简单但不起作用的事情,但我不明白为什么它不起作用。

  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)
        })
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这给了我以下错误:

    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
    
    Run Code Online (Sandbox Code Playgroud)
  2. 我想我可能需要move关闭,以便它获得以下所有权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
    
    Run Code Online (Sandbox Code Playgroud)

    然而,这也没有奏效。它失败并显示以下消息:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    此错误消息表示a2b未实现Copy,我想这是有道理的,但我不知道如何修复它。

  3. 无奈之下,我尝试了以下方法:

    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)
        })
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    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>| {
       |              ^^^^^^^^^^^^^^^^^^^^^^^
    
    Run Code Online (Sandbox Code Playgroud)

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

  4. 我尝试过的另一件事不起作用是:

    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
    
    Run Code Online (Sandbox Code Playgroud)

    这给了我以下错误:

    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
    
    Run Code Online (Sandbox Code Playgroud)

这是一个具有这些功能的游乐场


我不认为我对生命周期和所有权有足够的理解来了解这些功能中的每一个失败的原因。

我有点了解map_option_1map_option_3失败,因为move没有使用显式移动所有权,但我很奇怪map_option_2map_option_4失败。

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

为什么这些map_option_X函数中的每一个都无法编译

Fra*_*gné 6

我想我可能需要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))
    })
}
Run Code Online (Sandbox Code Playgroud)

如果内部闭包a2b按值捕获而外部闭包也是一个move闭包,则两个闭包最终都a2b按值捕获。根据所有权规则,一次只能拥有一个闭包a2b,因此当外部闭包被调用时,它会a2b移出自身(解构外部闭包)并进入内部闭包(这仅适用于FnOnce闭包,因为他们采取self而不是&mut self&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))
    })
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们讨论为什么map_option有效而map_option_2无效。问题源于Option::map拥有闭包参数的事实。因此,我们最终处于与map_option_5上述类似的情况。Option::map需要一个FnOnce,因为它最多只需要调用一次。但是,更改a2b为 aBox<FnOnce(A) -> B>无济于事,因为它实际上可以用于对map.

有一种方法可以避免内部闭包:将引用传递a2bmap。这有效,因为

  1. Box<F> where F: Fn<A>实施Fn<A>
  2. &F where F: Fn<A>实现FnOnce<A>(还有Fn<A>,尽管这在这里无关紧要)。
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)
    })
}
Run Code Online (Sandbox Code Playgroud)

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

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