为什么 `FnMut` 闭包不允许对捕获变量的共享引用转义?

Why `FnMut` closure does not allow shared reference to captured variable to escape?

我想了解为什么共享引用 &s 在这里不起作用。据我了解,传递给 map 的闭包拥有 s,因此 return 共享对 s 的引用应该没问题。它不会尝试多次移动 s 或在此处生成多个可变引用。 那么为什么会出错呢?抱歉,如果代码似乎没有意义,它只是用来作为示例!

    fn take_ref_produce_impl_trait(s: &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(&s)
            });
    }
error: captured variable cannot escape `FnMut` closure body
  --> src\experiments\closure.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(&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 尝试 return 它掉落的东西,上面的例子会报告同样的错误。

所以如果导致先前错误的原因是因为可以在迭代器之前删除闭包,那么在这种情况下是否也是出于同样的原因?

如果 Then 是流并且它的字段 f 是我们传递给 then 的闭包,那么当流尝试生成它的项目时闭包不会被删除。然而,该项目是一个未来,它被放置在 future 字段中。是不是因为掉Then后可以评估future?

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: &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(&s)
            })
            .flatten()
    }
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
  --> src\experiments\closure.rs:73:48
   |
73 |                 take_ref_produce_impl_trait_v2(&s)
   |                                                ^^
   |
note: first, the lifetime cannot outlive the lifetime `'_` as defined on the body at 72:19...
  --> src\experiments\closure.rs:72:19
   |
72 |             .then(move |_| {
   |                   ^^^^^^^^
note: ...so that closure can access `s`
  --> src\experiments\closure.rs:73:48
   |
73 |                 take_ref_produce_impl_trait_v2(&s)
   |                                                ^^
   = note: but, the lifetime must be valid for the static lifetime...
note: ...so that return value is valid for the call
  --> src\experiments\closure.rs:69:37
   |
69 |     async fn produce_stream_v2() -> impl Stream<Item=String> {
   |                                     ^^^^^^^^^^^^^^^^^^^^^^^^

假设您 .collect()produce_stream 中编辑了迭代器而不是删除它。然后你会有一个 impl IntoIterator<Item=String> + '_.

的集合

但是那些迭代器,其推断的 '_ 生命周期,借用 s,当 produce_streammap 闭包是时被删除。也就是说,闭包是 returning 对它拥有的字符串的引用,但引用比闭包更有效。

如果将闭包更改为 notmove 闭包,那么它应该可以编译,因为闭包和整个迭代器现在从局部变量 s.

但是您将无法 return 来自 produce_stream 的那个迭代器,如果那是您希望做的;在这种情况下,您需要安排 take_ref_produce_impl_trait 到 return 一个迭代器,该迭代器 拥有 它正在访问的字符串数据(或者可能是 Rc it), 所以迭代器没有生命周期要求。

关于我添加的第二个例子,我做了一些搜索,我想分享我的发现。

在这个link中我发现了一条评论,我认为这是对这里提出的所有问题的最终答案,我引用如下,

I don’t think it’s possible with how the FnMut trait is defined:

fn call_mut<'a>(&'a mut self, args: Args) -> Self::Output;

The &'a mut self argument is restricted to lifetime 'a, chosen freely by the caller. There is no way for Self::Output to declare dependence on the lifetime 'a. You can’t really say type Output = &'a i32 since the 'a isn’t even in scope yet!

所以简而言之,根据FnMut的特征定义,它的Output与闭包本身的生命周期无关。所以没有办法说 FnMut 闭包会比它在 Rust 中产生的东西长寿。