为什么 `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_stream
的 map
闭包是时被删除。也就是说,闭包是 returning 对它拥有的字符串的引用,但引用比闭包更有效。
如果将闭包更改为 not 是 move
闭包,那么它应该可以编译,因为闭包和整个迭代器现在从局部变量 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 中产生的东西长寿。
我想了解为什么共享引用 &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_stream
的 map
闭包是时被删除。也就是说,闭包是 returning 对它拥有的字符串的引用,但引用比闭包更有效。
如果将闭包更改为 not 是 move
闭包,那么它应该可以编译,因为闭包和整个迭代器现在从局部变量 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 中产生的东西长寿。