Почему закрытие `FnMut` не позволяет избежать общей ссылки на захваченную переменную?

#rust #closures #lifetime #ownership

Вопрос:

Я пытаюсь понять, почему общая ссылка amp;s здесь не работает. Насколько я понимаю, закрытие передано в map собственность s , поэтому должно быть нормально возвращать что-то, на что есть общая ссылка s . Здесь он не пытается перемещаться s несколько раз или создавать несколько изменяемых ссылок. Так почему же произошла ошибка? Извините, если код кажется бессмысленным, он просто используется в качестве примера!

     fn take_ref_produce_impl_trait(s: amp;str) -> impl IntoIterator<Item=String>   '_ {
        (0..s.len()).map(move |it| s[it..it 1].to_string())
    }

    fn produce_stream() {
        let s = "bbb".to_string();
        (1..3)
            .map(move |_| {
                take_ref_produce_impl_trait(amp;s)
            });
    }
 
 error: captured variable cannot escape `FnMut` closure body
  --> srcexperimentsclosure.rs:61:17
   |
58 |         let s = "bbb".to_string();
   |             - variable defined here
59 |         (1..3)
60 |             .map(move |_| {
   |                         - inferred to be a `FnMut` closure
61 |                 take_ref_produce_impl_trait(amp;s)
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-^
   |                 |                            |
   |                 |                            variable captured here
   |                 returns a reference to a captured variable which escapes the closure body
   |
   = note: `FnMut` closures only have access to their captured variables while they are executing...
   = note: ...therefore, they cannot allow references to captured variables to escape
 

—— ОБНОВЛЕНИЕ ——

Извините за это, но вот еще один пример, который приводит к подобной ошибке, на самом деле, приведенный выше пример сообщит о той же ошибке, если produce_stream попытается вернуть вещь, которую он уронил.

Итак, если причина, приведшая к предыдущей ошибке, заключалась в том, что закрытие может быть удалено перед итераторами, то было бы это по той же причине в данном случае?

Если Then поток и его поле f -это закрытие , в которое мы перешли then , то закрытие не сбрасывается, пока поток пытается создавать свои элементы. Однако товар-это будущее, которое помещается в future поле. Это потому, что будущее можно оценить после Then того, как оно отброшено?

Структура Then

 pin_project! {
    /// Stream for the [`then`](super::StreamExt::then) method.
    #[must_use = "streams do nothing unless polled"]
    pub struct Then<St, Fut, F> {
        #[pin]
        stream: St,
        #[pin]
        future: Option<Fut>,
        f: F,
    }
}
 

Пример

     use futures::{StreamExt, Stream};
    use futures::{stream};

    async fn take_ref_produce_impl_trait_v2(s: amp;str) -> impl Stream<Item=String>   '_ {
        stream::iter(0..s.len()).map(move |it| s[it..it 1].to_string())
    }

    async fn produce_stream_v2() -> impl Stream<Item=String> {
        let s = "bbb".to_string();
        stream::iter(1..3)
            .then(move |_| {
                take_ref_produce_impl_trait_v2(amp;s)
            })
            .flatten()
    }
 
 error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> srcexperimentsclosure.rs:73:48
   |
73 |                 take_ref_produce_impl_trait_v2(amp;s)
   |                                                ^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 72:19...
  --> srcexperimentsclosure.rs:72:19
   |
72 |             .then(move |_| {
   |                   ^^^^^^^^
note: ...so that closure can access `s`
  --> srcexperimentsclosure.rs:73:48
   |
73 |                 take_ref_produce_impl_trait_v2(amp;s)
   |                                                ^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that return value is valid for the call
  --> srcexperimentsclosure.rs:69:37
   |
69 |     async fn produce_stream_v2() -> impl Stream<Item=String> {
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^
 

Ответ №1:

Предположим, вы .collect() ввели итератор produce_stream вместо того, чтобы удалить его. Тогда у вас была бы целая коллекция impl IntoIterator<Item=String> '_ .

Но эти итераторы с их предполагаемым '_ сроком службы заимствуют у s , который отбрасывается при produce_stream map закрытии. То есть закрытие возвращает ссылки на строку, которой оно владеет, но ссылки переживают закрытие.

Если вы измените закрытие, чтобы оно не было move закрытием, то оно должно быть скомпилировано, потому что закрытие и общий итератор теперь заимствуются из локальной переменной s .

Но вы не сможете вернуть этот итератор produce_stream , если вы надеялись это сделать; в этом случае вам нужно будет организовать take_ref_produce_impl_trait возврат итератора, которому принадлежат строковые данные, к которым он обращается (или, возможно Rc , их часть), чтобы итератор не требовал времени жизни.

Комментарии:

1. спасибо за быстрый ответ, и вы хорошо все объяснили. Но вы не возражаете, если я побеспокою вас более сложным примером, прежде чем отдать вам должное? Я думаю, мне не следует менять операцию таким образом, извините за это! Я обновил новый пример в ОП.

2. @Rafoul Извините, но я не знаком с потоками и не могу дать вам хороший ответ в этом случае. И вы правы, думая, что вам не следует изменять вопрос таким образом.

3. Неважно, вы уже ответили на вопрос и помогли мне посмотреть в правильном направлении!

Ответ №2:

Что касается второго примера, который я добавил, я провел некоторый поиск и хотел бы поделиться тем, что я нашел.

По этой ссылке я нашел комментарий, который, по моему мнению, является окончательным ответом на все поднятые здесь вопросы, я цитирую его следующим образом,

Я не думаю, что это возможно с тем, как определяется черта FnMut:

fn call_mut<‘a>(amp;’a mut self, args: Args) -<‘a>> Self::Вывод;

Аргумент amp;’a mut self ограничен временем жизни ‘a, свободно выбираемым вызывающим. Для Self::Output нет возможности объявить зависимость от времени жизни ‘a. Вы действительно не можете сказать, введите Output = amp;’a i32, так как ‘a еще даже не в области видимости!

Короче говоря, основываясь на определении признака FnMut , он Output не имеет никакого отношения к сроку службы самого закрытия. Таким образом, невозможно сказать, что FnMut закрытие переживет то, что оно производит в ржавчине.