#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
закрытие переживет то, что оно производит в ржавчине.