В чем разница между временем жизни между asyn c fn и asyn c замыканием? - PullRequest
1 голос
/ 19 марта 2020

Посмотрите на этот код:

#![feature(async_closure)]

use std::future::Future;
use std::pin::Pin;

trait A<'a> {
    fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>>;
}

impl <'a, F, Fut> A<'a> for F
where Fut: 'a + Future<Output=()>,
      F: Fn(&'a i32) -> Fut
{
    fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>> {
        Box::pin(self(data))
    }
}

async fn sample(_data: &i32) {

}

fn is_a(_: impl for<'a> A<'a>) {

}

fn main() {
    is_a(sample);
    is_a(async move |data: &i32| {
        println!("data: {}", data);
    });
}

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

Почему работает is_a(sample), но следующая строка не компилируется? В чем разница времени жизни между asyn c fn и asyn c closure?

Версия закрытия завершается с ошибкой:

error: implementation of `A` is not general enough
  --> src/main.rs:29:5
   |
6  | / trait A<'a> {
7  | |     fn call(&'a self, data: &'a i32) -> Pin<Box<dyn 'a + Future<Output=()>>>;
8  | | }
   | |_- trait `A` defined here
...
29 |       is_a(async move |data: &i32| {
   |       ^^^^ implementation of `A` is not general enough
   |
   = note: `A<'1>` would have to be implemented for the type `[closure@src/main.rs:29:10: 31:6]`, for any lifetime `'1`...
   = note: ...but `A<'_>` is actually implemented for the type `[closure@src/main.rs:29:10: 31:6]`, for some specific lifetime `'2`

1 Ответ

3 голосов
/ 19 марта 2020

Возвращаемый тип закрытия async || - это анонимный тип, сгенерированный компилятором.

Такой тип реализует Future и захватывает дополнительный срок жизни, связанный с областью применения закрытия async ||. .

fn main() {

    let closure = async move |data: &i32| {   --+ '2 start
        println!("data: {}", data);             |
    };                                          |
                                                |
    is_a(closure);                              |
                                                v 
}

Блок asyn c возвращает тип с подписью, например:

impl Future<Output = SomeType> + '2 + '...

Где '2 - время жизни замыкания.

Примечание что при использовании функции asyn c вместо замыкания этого дополнительного срока службы нет.

Когда вы вызываете is_a, например:

let closure = async move |data: &i32| {
    println!("data: {}", data);
};

is_a(closure);

Вы получаете:

error: implementation of `A` is not general enough
...
= note: `A<'1>` would have to be implemented for the type `[closure@src/main.rs:43:19: 45:6]`, for any lifetime `'1`...
= note: ...but `A<'_>` is actually implemented for the type `[closure@src/main.rs:43:19: 45:6]`, for some specific lifetime `'2`

, поскольку аргумент closure является типом, реализованным для определенного c времени жизни '2, но требуется любое время жизни:

fn is_a(_: impl for<'a> A<'a>) {}

Обратите внимание, что ошибка, которую вы действительно заметили скрывает другое нарушение времени жизни, которое возникает из захваченного '2 времени жизни.

Для:

let closure = async move |data: &i32| {
    println!("data: {}", data);
};

компилятор сообщает:

error: lifetime may not live long enough
  --> src/main.rs:43:19
   |
43 |     let closure = async move |data: &i32| {
   |                   ^^^^^^^^^^^^^^^^^^-^^^-
   |                   |                 |   |
   |                   |                 |   return type of closure is impl std::future::Future
   |                   |                 let's call the lifetime of this reference `'1`
   |                   returning this value requires that `'1` must outlive `'2`

, что позволяет мне заключить, что оно невозможно использовать ссылки на аргументы внутри async || clos

Зачем нужен срок службы '2?

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

fn my_function() {

    let value = 1                           --+ '1 start           
                                              |
    let closure = async move |data: &i32| {   |       --+ '2 start
        println!("data: {}", data);           |         |
    };                                        |         |
                                              |         |
    tokio::spawn(closure(&value))             |         |
                                             -+ '1 end  |
}                                                       v continue until   
                                                          the future complete

Рассмотрим приведенный выше пример: значение - это слот памяти, выделенный в стеке, и он будет действителен до тех пор, пока не вернется my_function и стек не размотается.

Время жизни '1 take учетная область действия value, когда my_function возвращает ссылку &value не является более действительным.

Но откуда идет жизнь '2?

Это потому что closure(&value) возвращает сущность, которая реализует Future, который будет жить в исполнителе времени выполнения, в данном случае в tokio executor, до тех пор, пока не закончится вычисление.

Время жизни '2 примет эту область действия действительность Future во внимание.

Для обоснования необходимости '2 пожизненной необходимости рассмотрите следующий сценарий:

fn run_asyn_closure() {
    let data: i32 = 1;

    let closure = async move |data: &i32| {
        println!("starting task with data {}", data);

        // yield the computation for 3 seconds, awaiting for completion of long_running_task
        long_running_task().await;

        // data points to a memory slot on the stack that meantime is rewritten
        // because run_asyn_closure returned 3 seconds ago
        println!("using again data: {}", data); // BANG!! data is not more valid
    };

    tokio::spawn(closure(&data));
}

Обратите внимание, что в действительности tokio::spawn необходимо, чтобы &data ссылка имеет 'static время жизни, но это не имеет отношения к пониманию этой темы.

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