Почему переменная не может быть перемещена из замыкания? - PullRequest
3 голосов
/ 18 января 2020

У меня есть следующая функция:

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
    

Вот детская площадка с каждой из этих функций.


Я не думаю, что я иметь достаточно хорошее представление о временах жизни и владении, чтобы понять, почему каждая из этих функций не работает.

Я могу понять, как map_option_1 и map_option_3 не работают, потому что move не используется для явного перемещения владение, но я удивлен, что map_option_2 и map_option_4 терпят неудачу.

Я очень удивлен, что map_option_2 не работает, но фактическая функция map_option работает. Для меня это практически одна и та же функция.

Почему каждая из этих map_option_X функций не компилируется ??

1 Ответ

6 голосов
/ 18 января 2020

Я подумал, что мне может понадобиться 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, а не &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))
    })
}

Теперь давайте обсудим, почему map_option работает, пока map_option_2 нет. Проблема связана с тем, что Option::map становится владельцем аргумента замыкания. Таким образом, мы оказываемся в ситуации, аналогичной map_option_5 выше. Option::map занимает FnOnce, потому что это нужно только вызвать его не более одного раза. Изменение a2b на Box<FnOnce(A) -> B> не поможет, потому что его можно использовать во многих вызовах map.

Существует способ избежать внутреннего закрытия: передайте ссылку на a2b до map. Это работает, потому что

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

Закрытие по-прежнему становится владельцем a2b, но оно не потребляет при вызове, поэтому замыкание может вызываться несколько раз.

map_option работает, потому что его внешнее замыкание не должно потреблять a2b. Внутренняя крышка фиксирует a2b по ссылке от внешней крышки. Это работает, потому что для вызова Fn замыкания требуется только общая ссылка на замыкание.

...