Невозможно выйти из захваченных переменных в закрытии `FnMut` - PullRequest
0 голосов
/ 29 октября 2018
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, и Closure B модифицируют notificator, таким образом, ошибка.

Правильно ли мое понимание? Как я могу решить эту проблему?

1 Ответ

0 голосов
/ 29 октября 2018

Вложенные замыкания сложны.

Учтите это:

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

Игровая площадка

Обратите внимание, что внутреннее закрытие захватывает 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
    });
  });
}

Детская площадка

Затем вы получите сообщение об ошибке (за исключением Fn против FnMut, что не имеет значения для проблемы). Внутреннее закрытие создается заново при каждом обращении к внешнему закрытию (так должно быть, потому что оно должно захватывать a каждый раз), но оно пытается захватить outer_s путем перемещения каждый раз. Это не может работать; после первого раза outer_s перемещается и, таким образом, становится недействительным.

Чтобы сопоставить это с вашим кодом, было бы неправильно говорить "Closure B захватывает notificator", потому что не существует только одного Closure B. Там столько столько, сколько необходимо, однако часто ваши вложенные and_then и for_each звонки будут в конечном итоге в этом куске кода. Но только один может захватить с ходу.

Итак, чтобы решить эту проблему, вам нужно либо убедиться, что есть только одно Замыкание B, либо убедиться, что у вас достаточно mpsc::Sender s для каждого.

Первый способ заключается в извлечении замыкания из вложенного контекста.

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

за исключением того, что это не сработает, поскольку теперь Closure A сталкивается с той же проблемой, поэтому вам придется делать это несколько раз:

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() вызовов, и, поскольку я не могу проверить ваш код, я не буду пытаться его написать.

Однако, когда все сказано и сделано, ваш код все равно потерпит неудачу, потому что вы выходите из Proxy, одновременно пытаясь его использовать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...