async fn 和 async closure 之间的生命周期推断有什么区别?

What's the difference of lifetime inference between async fn and async closure?

看这段代码:

#![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);
    });
}

Playground

为什么is_a(sample)可以编译下一行却编译失败? async fn 和 async 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`

async || 闭包的 return 类型是编译器生成的匿名类型。

这种类型实现了一个 Future 并且它捕获了一个额外的 与 async || 闭包范围相关的生命周期。

fn main() {

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

异步块return是一种签名类型,如:

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

其中 '2 是闭包的生命周期。

请注意,使用异步函数而不是闭包时,没有额外的生命周期要求。

当您这样调用 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 参数是为特定生命周期实现的类型 '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 || 闭包中使用参数引用。

为什么需要生命周期 '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

考虑上面的例子:value是分配在栈上的一个内存槽,直到 my_function return 秒,堆栈展开。

生命周期'1考虑value的有效期,当my_functionreturns 引用 &value 无效。

但生命从何而来'2?

这是因为 closure(&value) return 是一个实现 Future 的实体,它将存在于运行时执行器中, 在本例中是 tokio 执行器,直到计算结束。

'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 生命周期, 但这与理解这个主题无关。